关于ThreadLocal的实现原理以及ThreadLocal为什么会造成内存泄露

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq32933432/article/details/80212873

用处

可以私有化存储线程的变量值

用法

static class ResourceClass {

        public final static ThreadLocal<String> RESOURCE_1 =
                                       new ThreadLocal<String>();

        public final static ThreadLocal<String> RESOURCE_2 =
                                       new ThreadLocal<String>();

    }

    static class A {

        public void setOne(String value) {
            ResourceClass.RESOURCE_1.set(value);
        }

        public void setTwo(String value) {
            ResourceClass.RESOURCE_2.set(value);
        }
    }

    static class B {
        public void display() {
            System.out.println(ResourceClass.RESOURCE_1.get()
                        + ":" + ResourceClass.RESOURCE_2.get());
        }
    }

    public static void main(String []args) {
        final A a = new A();
        final B b = new B();
        for(int i = 0 ; i < 15 ; i ++) {
            final String resouce1 = "线程-" + I;
            final String resouce2 = " value = (" + i + ")";
            new Thread() {
                public void run() {
                try {
                    a.setOne(resouce1);
                    a.setTwo(resouce2);
                    b.display();
                }finally {
                    ResourceClass.RESOURCE_1.remove();
                    ResourceClass.RESOURCE_2.remove();
                }
            }
        }.start();
        }
    }

运行结果

线程-4: value = (4)
线程-2: value = (2)
线程-5: value = (5)
线程-1: value = (1)
线程-0: value = (0)
线程-3: value = (3)
线程-6: value = (6)
线程-7: value = (7)
线程-10: value = (10)
线程-11: value = (11)
线程-9: value = (9)
线程-12: value = (12)
线程-13: value = (13)
线程-8: value = (8)
线程-14: value = (14)

可以看到线程是交替执行的,但是每个线程取到的值却没有乱,说明已经做到了线程的私有化。

实现原理

看ThreadLocal的set方法源码

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
        if (map != null)
            map.set(this, value);//以当前ThreadLocal对象为键,value为值存储到map中
        else
            createMap(t, value);
    }

ThreadLocal的get方法源码

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//以当前ThreadLocal对象为键,获取到map中的对象
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

通过上述源码可以看到,实际上是每个Thread线程维护着一个ThreadLocalMap,而ThreadLocalMap中的一个键即是一个ThreadLocal,value是我们通过set方法传进去的参数。看下面的图会好理解一点
这里写图片描述
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。

关于内存泄漏

为什么会造成内存泄漏

下面字字精华,请耐心读完
我们上面说了ThreadLocal 本身并不存储值,它只是作为一个 key保存到ThreadLocalMap中,但是这里要注意的是它作为一个key用的是弱引用(什么是强引用,软引用,弱引用,虚引用)本处不做阐述。
因为没有强引用链,弱引用在GC的时候可能会被回收。这样就会在ThreadLocalMap中存在一些key为null的键值对(Entry)。因为key变成null了,我们是没法访问这些Entry的,但是这些Entry本身是不会被清除的,为什么呢?因为存在一条强引用链。即线程本身->ThreadLocalMap->Entry也就是说,恰恰我们在使用线程池的时候,线程使用完了是会放回到线程池循环使用的。由于ThreadLocalMap的生命周期和线程一样长,如果没有手动删除对应key就会导致这块内存即不会回收也无法访问,也就是内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。但是这些举动不能保证内存就一定会回收,因为可能这条线程被放回到线程池里后再也没有使用,或者使用的时候没有调用其get(),set(),remove()方法。

为什么使用弱引用,内存泄漏是否是弱引用的锅?

下面我们分两种情况讨论:

(1)key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

(2)key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set、get、remove的时候会被清除。

因此:内存泄漏归根结底是由于ThreadLocalMap的生命周期跟Thread一样长。如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

如何避免内存泄漏

每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
注意:并不是所有使用ThreadLocal的地方,都在最后remove(),他们的生命周期可能是需要和项目的生存周期一样长的,所以要进行恰当的选择,以免出现业务逻辑错误!

写在最后

看来好像没有什么更好的办法能避免这个问题,小生不才,希望能有指正。

参考原文:https://blog.csdn.net/bntX2jSQfEHy7/article/details/78315161

猜你喜欢

转载自blog.csdn.net/qq32933432/article/details/80212873