做Java开发的对ThreadLocal的肯定不会陌生, 它的作用是提供线程内的局部变量,这种变量在多线程环境下访问时能够保证各个线程里变量的独立性。也是Java面试的必备考点。
我们知道SimpleDateFormat是非线程安全,在多线程使用下可能会有问题。
针对这个问题,可以有几种解决方案:
每次要使用 SimpleDateFormat 时都创建一个局部的 SimpleDateFormat对象。局部变量,自然就不存在线程安全的问题了。但如果需要频繁进行调用的话,每次都要创建新的对象,开销太大。
就是对 SimpleDateFormat 进行加锁,这样可以确保同一时间只有一个线程可以持有锁,进而解决线程安全的问题。但是这种方法在多线程竞争激烈的时候会带来效率问题
就是使用 ThreadLocal。 ThreadLocal 可以确保每个线程都可以得到单独的一个 SimpleDateFormat 的对象,那么自然也就不存在竞争问题了。
TheadLocal的内部结构示意图如下:
从结构示意图真正存储值得地方是放在了线程对象中,Thread对象中有个ThreadLocalMap对象,该对象中的key是ThreadLocal对象,value是用户真正存储的值。
以ThreadLocal 中的set为例:
在ThreadLocal的 set方法中,首先获取了当前线程,然后获取线程中的ThreadLocalMap,如果map不会null,则直接put到该map中,其中key就是threadLocal对象,value即用户set的值。
如果ThreadLocalMap中的key和value都是普通对象,可能会存在什么问题呢?
当用户如果忘记remove对象时并且该线程一直存在时,就会就会造成线程对key、value的引用一直存在造成内存泄漏。
为了避免内存泄漏,ThreadLocalMap中的key采用了WeakReference。
这样保证了即使用户忘记调用remove,也能保证key被回收调。
那么value怎么办呢,因为value不是 WeakReference,那么JDK是如何保证value不存在内存泄漏呢?
其实在 ThreadLocalMap有个expungeStaleEntry, 这个方法在ThreadLocalMap get、set、remove、rehash等方法都会调用到。 看下面标红的两处代码,第一处是将remove的entry赋空,第二次处是找到已经被GC的ThreadLocal,然后会清理掉table数组对entry的引用。这样entry在后续的GC中就会被回收。
在使用完成之后,要显示的调用remove释放。
官方推荐使用private static修饰。其中private比较容易理解;加上static之后,就不会频繁的创建弱引用,这样就能减少弱引用对GC的影响。