谈谈ThreadLocal的设计及不足
用Java语言开发的同学对 ThreadLocal
应该都不会陌生,这个类的使用场景很多,特别是在一些框架中经常用到,比如数据库事务操作,还有MVC框架中数据跨层传递。这里我们简要探讨下 ThreadLocal
的内部实现及可能存在的问题。
首先问自己一个问题,让自己实现一个这个的功能类的话怎么去做?第一反应就是简单构造一个 Map<Thread, T>
数据结构,key是 Thread
,value就是我们要保存的线程变量 T
。我们看下这种设计有哪些问题:
- 随着运行时间越久,存在Map里的Thread越多,当Thread退出时,资源也没有释放,存在内存泄漏问题
- Map数据因为会被多线程访问,存在资源竞争,所以还必需对Map做同步安全操作,效率低下
JDK中的 ThreadLocal
精妙的设计来解决问题上述两个问题。首先每个Thread(线程)内部都有一个Map结构数据ThreadLocalMap<ThreadLocal, T>
,当我们对线程变量赋值时ThreadLocal.set(T value)
时,其实是先获取当前线程Thread.currentThread())
的内部属性字段ThreadLocalMap
,然后以当前ThreadLocal
为key设置线程变量值T。这种设计的精髓是,每个Thread
线程都维护一份自己的ThreadLocalMap
数据结构,这样就解决了上面所述问题中的第二个,不存在竞争条件。
既然每个Thread
内部都维护一个ThreadLocalMap
字典数据结构,字典的Key值是ThreadLocal
,那么当某个ThreadLocal
对象不再使用(没有其它地方再引用)时,每个已经关联了此ThreadLocal
的线程怎么在其内部的ThreadLocalMap
里做清除此资源呢?JDK中的ThreadLocalMap
又做了一次精彩的表演,它没有继承java.util.Map
类,而是自己实现了一套专门用来定时清理无效资源的字典结构。其内部存储实体结构Entry<ThreadLocal, T>
继承自java.lan.ref.WeakReference
,这样当ThreadLocal
不再被引用时,因为弱引用机制原因,当jvm发现内存不足时,会自动回收弱引用指向的实例内存,即其线程内部的ThreadLocalMap
会释放其对ThreadLocal
的引用从而让jvm回收ThreadLocal
对象。这里是重点强调下,是回收对ThreadLocal
对象,而非整个Entry
,所以线程变量中的值T
对象还是在内存中存在的,所以内存泄漏的问题还没有完全解决。接着分析JDK的实现,会发现在调用ThreadLocal.get()
或者ThreadLocal.set(T)
时都会定期执行回收无效的Entry
操作。所以这就解决了上述问题中的第1个问题。
问题真的都解决了吗,好像都解决了。因为即没有竞争资源操作,也不会存在内存泄漏。但是细想一下,总感觉哪里不对劲,真的不会存在内存溢出(OOM)问题吗?上面一段的分析中,强调ThreadLocalMap
会定期清理内部的无效Entry
对象,触发的条件就是对TrheadLocal
执行 set,get,remove()等操作时会触发,但是如果存在这样的场景,当我们在某个线程上下文中执行ThreadLocal.set(T)
设置了一个很大内在的数据结构,然后该ThreadLocal
被清除引用回收,之前的线程又一直存活着,则这个大内存数据对象T
是一直不回收的,这里很容易写个代码测试出OOM来。怎么解决这个问题呢?
Lucene中的org.apache.lucene.util.CloseableThreadLocal
类解决了上述特殊场景引起的问题:即解决JDK中因为定期才执行无效对象回收的问题。CloseableThreadLocal
在内部维护了一个ThreadLocal
,当执行CloseableThreadLocal.set(T)
时,内部其实只是代理的把值赋给内部的ThreadLocal
对象,即执行ThreadLocal.set(new WeakReference(T))
。看到这里应该明白了,这里不是直接存储T
,则是包装成弱引用对象,目的就是当内存不足时,jvm可以回收此对象。但是细心的你会发现会引入一个新的问题,即当前线程还存活着的时候,因为内存不足而回收了弱引用对象,这样会在下次调用get()
时取不到值返回null,这是不可接受的。所以CloseableThreadLocal
在内部还创建了一个数据,WeakHashMap<Thread, T>
,当线程只要存活时,则T就至少有一个引用存在,所以不会被提前回收。但是又引入的第2个问题,对WeakHashMap
的操作要做同步synchronized
限制。你看,所有的东西都不是十全十美的,我们掌握那个平衡点就行了。