最近看到一篇关于ThreadLocal的文章,比较有意思,也有些感悟,正好写下来
1.什么是ThreadLocal
我第一次看到这个名字,就猜和线程Thread有关,翻译为本地线程。后来看了下,发现它真实的作用是线程变量副本,或者提供线程变量隔离的能力。
2.ThreadLocal的作用及机制
顾名思义,ThreadLocal有一种能力,对于同一个对象,不同线程下可以独立的变化而不影响其他线程的对象。
那么它是怎么做到的呢?很简单,每个线程有个Map存储对象信息。比如A线程有个AMap,B线程有个BMap,当线程要存储对象CObject时,AMap通过map.put(key,CObject)方法将对象存储起来,同理BMap也将对象存储起来。这样,两个对象分别将对象存储起来了,就达到隔离的目的了。
这个时候有个新问题产生了,线程中Map的key是谁?很明显,这个key基本只有两种可能,一个是本地Thread,另一个是ThreadLocal。那么假设是Thread,那么有另外一个变量怎么办,这样会把原来的变量覆盖掉,所以只能把ThreadLocal当做key。当需要保存多个变量时,新建多个ThreadLocal就可以了。那么这个问题可以优化吗?这个后面再说
3.Thread中Map的作用和结构
Thread中的Map全名叫ThreadLocalMap,是ThreadLocal内部类。ThreadlocalMap只有一个作用,那就是保存线程变量。
它内部结构,是数组实现的,但是它和HashMap不一样,它采用的是开放定址法。就是当它的地址发生冲突后,会将其往后移一位。如果后面还有冲突,继续往后移动。如果长度到底,则进行扩容。
4.ThreadLocal的内存泄漏问题
ThreadLocalMap的key是WeakReference类型(Java引用类型),这样可以尽最大努力避免内存泄漏。如图所示:

从图中可以看到,首先ThreadLocal是个强引用,而且又被ThreaLocalMap的key引用,这个是WeakRefrence引用。只要ThreadLocal被强引用着,GC不会回收ThreadLocal对象。
那这样设计WeakRefrence还有什么作用呢?根据JVM根搜索算法,在线程中,一直存在Thread -> ThreadLocalMap -> Entry这样一条引用链路。如果key不设计成WeakRefrence类型,就一直不会被GC回收,key就一直不会是null,那么Entry元素就一直不会被清理。所以ThreadLocal的设计者认为只要ThreadLocal所在的作用域结束了工作被清理了,GC回收的时候就会把key引用回收,key置为null,ThreadLocal会尽力保证Entry清理掉来最大可能避免内存泄漏。
那么如何解决ThreadLocal内存泄漏问题?可以手动调用remove()方法。
5.ThreadLocal多个对象问题?
前面说过,如果要存储多个对象,那么我们就要新建多个ThreaLocal对象,那么有什么解决办法呢?有的,再封装一下,只要把value设置成Map就可以了。
网友评论