并发之ThreadLocal
ThreadLocal
ThreadLocal 用一种存储变量与线程绑定的方式,在每个线程中用自己的 ThreadLocalMap 安全隔离变量,为解决多线程程序的并发问题提供了一种新的思路。
简单画一个UML:
再看下ThreadLocal源码get()与set()
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
set(T value):每次获取当前线程,从线程中获取其属性map,然后以当前threadLocal对象作为key,存放value;
get():每次获取当前线程,从线程中获取其属性map,然后以当前threadLocal对象作为key,获取value;
看下原理图:
从上面的结构图,我们已经窥见ThreadLocal的核心机制:
- 每个Thread线程内部都有一个Map。
- Map里面存储线程本地对象(key)和线程的变量副本(value)
- 但是,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
ThreadLocal 弱引用
从上图UML可看出,Entry
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
entry对象中存储了ThreadLocal对象的弱引用和这个ThreadLocal对应的value对象的强引用
分析:
假设我们在线程的run方法中调用了一个方法,并在这个方法中创建了ThreadLocal对象,并使用了他,内存结构示意图如下。
当这个方法结束时,这个方法中创建的ThreadLocal对象本身(图中绿色区域)就被垃圾回收器回收了,但是线程还没有结束,
所以ThreadLocalMap中还存在这个entry。由于entry中的key(即ThreadLocal对象)是弱引用类型,
所以此时调用entry.get()方法时就会返回null,内部结构如下图所示。
从图中我们可以看到value对象(红色区域)始终不能被回收,而我们再也不会使用它了,这就造成了内存泄露。
那Entry中为什么保存的是key的弱引用呢?其实这是为了最大程度上减少内存泄露,副作用是同时减少哈希表中的冲突。
当ThreadLocal对象被回收时,对应entry中的key就自动变成null(entry对象本身不为null)。若此后我们调用get,set或remove方法时,
就会尝试删除key为null的entry,以释放value对象所占用的内存。
总结
- 每个ThreadLocal只能保存一个变量副本,如果想要上线一个线程能够保存多个副本以上,就需要创建多个ThreadLocal。
- ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。
- 适用于无状态,副本变量独立后不影响业务逻辑的高并发场景。如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决,需要另寻解决方案。
参考:
https://www.jianshu.com/p/98b68c97df9b
http://www.cnblogs.com/nullzx/p/7553538.html