ThreadLocal使用和原理
实现机制
1、每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
2、当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。
public T get() { //获取当前线程 Thread t = Thread.currentThread(); //每个线程拥有一个map,取出来的是线程t的ThreadLocal.ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) { //this->ThreadLocal实例是作为map的key来使用的 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } //初始化这个线程的ThreadLocalMap,并且返回null 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; } protected T initialValue() { return null; }
3、当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) //一个thread.ThreadLocalMap 可以维护多个不同ThreadLocal的值 map.set(this, value); else createMap(t, value); }
事实上,从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己线程的变量map中以ThreadLocal为key来取值,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。
内存泄露
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
为什么用弱引用?
key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除,否则value内存溢出。
由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
使用举例
public class TestThreadLocal { private static int j = 0; public static void main(String[] args) { final ThreadLocal<String> tl1 = new ThreadLocal<String>(); final ThreadLocal<String> tl2 = new ThreadLocal<String>(); ExecutorService es = Executors.newFixedThreadPool(10); for (int i = 0; i < 30; i++) { es.execute(new Runnable() { @Override public void run() { synchronized (TestThreadLocal.class) { tl1.set("tl1"+"-"+j); tl2.set("tl2"+"-"+j); System.out.println("当前线程" + Thread.currentThread()+"-"+ j + "放入data=" + tl1.get()); System.out.println("当前线程" + Thread.currentThread()+"-"+ j + "放入data=" + tl2.get()); j++; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("当前线程" + Thread.currentThread() + ":取出data=" + tl1.get()); System.out.println("当前线程" + Thread.currentThread() + ":取出data=" + tl2.get()); } }); } } }