本章主要讲一下本地线程变量ThreadLocal
ThreadLocal是什么
在我们所知的并发中,多线程访问共享资源时,不能保证线程安全。一般使用者在访问共享变量的时候需要进行额外的同步操作才能进行操作。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。就是共享变量副本后存在线程自己内部。这样的话对变量的修改,不会影响到其他线程。
ThreadLocal实现原理
![](https://imgkr.cn-bj.ufileos.com/70b6841d-fcab-4bb4-bfb8-b022cd9acb17.png)
-
每一个thread线程中有一个threadlocalmap -
ThreadLocalMap 自身类似于是一个 Map,里面会有一个个 key value 形式的键值对.key指的就是threadlocal的引用。value就是存储的内容。
set源码
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else createMap(t, value); }
set 方法的作用是把我们想要存储的 value 给保存进去。首先是获取当前线程的引用,根据getMap方法和当前线程引用来获取ThreadLocalMap,以当前ThreadLoacl为key将值存储。
get源码
public T get() {
//获取到当前线程
Thread t = Thread.currentThread();
//获取到当前线程内的 ThreadLocalMap 对象,每个线程内都有一个 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
if (map != null) { //获取 ThreadLocalMap 中的 Entry 对象并拿到 Value ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //如果线程内之前没创建过 ThreadLocalMap,就创建 return setInitialValue(); }
get方法,先获取到当前线程的引用,通过getMap()方法和当前线程的引用来获取ThreadLocalMap。ThreadLocalMap其实类似于HashMap,可以根据getEntry的方式(参数是ThreadLocal,也就是this)来获取Entry对象,通过Entry.value来获取存储值
如果一个线程中需要多个线程变量?
根据上述的讲解,如果需要多个线程变量,需要定义多个ThreadLocal来使用。
ThreadLocal必须要remove()
ThreadLocal会导致内存泄漏
左侧是引用栈,栈里面有一个 ThreadLocal 的引用和一个线程的引用,右侧是我们的堆,在堆中是对象的实例。
我们重点看一下下面这条链路:Thread Ref → Current Thread → ThreadLocalMap → Entry → Value → 可能泄漏的value实例。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
ThreadLocalMap内部实际是个Entry数组,如上述代码所述,当前ThreadLocal的引用k被传递给WeakReference的构造函数,所以ThreadLocalMap中的key为ThreadLocal的弱引用。当一个线程调用ThreadLocal的set方法设置变量的时候,当前线程的ThreadLocalMap就会存放一个记录,这个记录的key值为ThreadLocal的弱引用,value就是通过set设置的值。
如果在没有其他地方引用这个ThreadLocal的话,gc会对弱引用的key进行回收,但是value不是弱引用。这是就会出现key为null,value不为null的entry项。造成内存泄漏
remove源码
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
remove其实就是将 key 所对应的 value 给清理掉。这样GC就能回收它了。
本文使用 mdnice 排版