深入并发之(二) ThreadLocal源码与内存泄漏相关分析

当前位置:首页百苑国际备用手机版 >

百苑国际备用手机版

深入并发之(二) ThreadLocal源码与内存泄漏相关分析

时间:2019-06-10本站浏览次数:191

       

深入并发二 ThreadLocal源码与内存泄漏相关分析

这篇文章的主要内容是介绍ThreadLocal类使用方法,源码实现,以及实际应用。ThreadLocal实际上是在多线程编程的过程中,每个线程用来保存局部变量的一个类,用这个类保存的变量在属于各个线程独有,不会互相影响,那么我们就可以实现不同线程保存同一个变量的不同值。

ThreadLocal的使用

ThreadLocal的使用十分方便,下面给出一个使用的例子,实现同一个变量在不同线程中有着不同的值,同时,这两个值不互相影响。

public static void main(String[] args) { ThreadLocal<Integer> value = ThreadLocal.withInitial(() -> { return 0; }); new Thread(() -> { value.set(10); //Thread-0 10 System.out.println(Thread.currentThread().getName() + " " + value.get()); }).start(); new Thread(() -> { //Thread-1 0 System.out.println(Thread.currentThread().getName() + " " + value.get()); value.set(3); //Thread-1 3 System.out.println(Thread.currentThread().getName() + " " + value.get()); }).start(); }

上面的代码中,我们定义了一个变量value,这个变量在不同线程中有不同的值,所以我们使用ThreadLocal,初始化这个值为0。上面的代码十分简单,就不做详细讲解了。

ThreadLocal源码分析

下面我们来分析一下ThreadLocal的底层实现。

实际上,每个Thread对象都持有一个ThreadLocalMap的对象,里面保存了所有ThreadLocal变量,这个map的key是ThreadLocal变量对象,而值就是这个线程中ThreadLocal对应的值。

ThreadLocalMap实际使ThreadLocal的一个静态内部类。

下面我们先来分析方法set()

public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //获取线程所持有的map对象 ThreadLocalMap map = getMap(t); if (map != null) //以当前ThreadLocal为key,将value值加入map中 map.set(this, value); else //如果map对象还没有,那么调用初始化方法,并且将值插入 createMap(t, value);}ThreadLocalMap getMap(Thread t) { //获取线程对象持有的map对象 return t.threadLocals;}void createMap(Thread t, T firstValue) { //初始化threadLocals t.threadLocals = new ThreadLocalMap(this, firstValue);}

然后我们来分析一下get()方法

public T get() { //获取当前线程 Thread t = Thread.currentThread(); //获取线程所持有的map对象 ThreadLocalMap map = getMap(t); if (map != null) { //取出map中key为当前ThreadLocal对象的Entry ThreadLocalMap.Entry e = map.getEntry(this); //如果存在,直接返回value if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //如果上面没有返回,证明还没有赋值,那么调用初始化的方法 return setInitialValue();}private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value;}

在这里我们可以看到如果我们调用过ThreadLocal对象的set方法给对象赋值的话,这是调用get方法去取值,会调用方法setInitialValue。所以,一般我们在初始化ThreadLocal对象的时候,会重写方法initialValue(),这样就不会发生get方法返回值为null的情况。

同时在java8之后,我们也可以采用最开始的例子中的方法来初始化ThreadLocal对象。

ThreadLocal<Integer> value = ThreadLocal.withInitial(() -> { return 0;});

ThreadLocalMap分析与ThreadLocal导致内存泄露的问题分析

这里我们不去分析ThreadLocalMap中方法的具体实现,它的大部分功能和一个普通的map相似,我们主要是要分析一下ThreadLocal导致内存泄漏的原因。

首先,给出关键部分的代码

static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }

注意,ThreadLocalMap中的key实际上是ThreadLocal对象的弱引用。

那么什么是弱引用呢,既然有弱引用必然就有强引用。

实际上强引用就是我们正常使用new关键字创建的引用,** 弱引用指的其实是WeakReference关键字包裹的引用,在GC的过程中,如果一个对象只有弱引用指向它的时候,这个对象就已经可以被GC回收了,而一个强引用只有当所有引用都不存在的时候才可以被回收。**

那么,在map中为什么要使用弱引用这种方式呢?请大家想想一种情况,我们有一个对象,这个对象有一个引用A,并且这个对象作为map的key存在,那么当我们不再使用这个对象的时候,我们将引用置为null,这是,假设map中的引用是强引用,那么由于map中依然有这个对象的引用,那么这个对象不能够被GC回收,这显然不是我们想要看到的场景,所以,一般来说map中的key一般使用弱引用,这样,当对象只有这一个引用的时候就可以及时被GC回收。

因此,我们就有HashMapWeakHashMap两个类,大家可以后续了解一下,两者的主要区别就在于key是强引用还是弱引用。 *

下面转回正题,关于ThreadLocal导致内存泄漏的问题。

在这里,key值实际上是ThreadLocal变量的弱引用,所以当我们的key变为空的时候这个引用就不存在了,那么我们也就无从得到value的值,这是value的值就变成了无法访问的值。

ThreadLocalMap中实际上已经考虑到了这个问题,当我们调用ThreadLocal中set、get和remove方法的时候,实际上是会检查key为null的情况,将这些内容清掉。

当线程的生命周期结束的时候后,实际上所有的ThreadLocalMap都会被回收,因此,这种情况下不会造成内存泄漏。

这里引用StackOverFlow中一位答主给出的情况,详情见 java - ThreadLocal & Memory Leak - Stack Overflow

这里给出翻译。

举一个例子:有一个服务器有一个线程池,这些线程会一直存活知道服务器停止。一个web应用在一个类中使用了一个static的ThreadLocal来存放一些线程局部变量,这个变量是web应用中里一个类的对象(SomeClass)。这些操作实在一个线程中进行的。根据定义,一个ThreadLocal的引用会一直存活,知道拥有这个对象的线程死亡或者ThreadLocal对象本身是不可达的。如果web应用在关闭之前没有成功清除ThreadLocal的引用,那么这时会发生十分糟糕的事情:因为线程不会死亡,并且ThreadLocal对象依然指向着的引用是static的,那么,虽然应用已经停止了,ThreadLocal对象依然指向着SomeClass的对象(一个web应用中的类)这种情况的结果就是,web应用中的classloader不会被GC,这就意味着web应用中所有的类(以及所有的静态类)都仍然被装载(这会影响到PermGen)每一次reload应用都会增加PermGen的使用,这样就会导致permgen leak

相信上面的解释已经十分清晰了。下面给出tomcat中出现的例子,这个bug已经被官方修复了。

MemoryLeakProtection - Tomcat Wiki

至此,我们对ThreadLocal的了解已经十分深入了,在我们使用ThreadLocal类的时候,一定要十分注意,防止发生内存泄漏。




公司地址:内蒙古自治省包头市青山区民主路43号
联系人:雷中 18548320414
陶相臣 13554506090
电话:18592771203 传真:iyau8rs1hi@163.com
邮箱:egws14qkgy@163.com

粤公网安备 44030702001579号

百苑国际官方线路@