看一下源码上的定义:
/** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable. {@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). */ 复制代码
英语比较差劲,凑合翻译一下,大意是threadLoacl会提供线程局部变量,这些变量不同于其他正常的变量,在每个线程中都会有一个独立的副本,可以通过set和get操作threadLocal中的数据,但是只会改变本线程中的数据,并不会影响其他线程;
理解起来可能有点绕,看一下代码,没有代码的解释是没有灵魂的。。。
public static void main(String[] args) { local=new ThreadLocal<>(); //在主线程中更改变量 local.set("我是main"); System.out.println("main:"+local.get()); //在A线程中程中更改变量 new Thread("ThreadA"){ @Override public void run() { super.run(); local.set("我是ThreadA"); System.out.println("ThreadA:"+local.get()); } }.start(); //在B线程中程中未对变量进行操作,也没有初始化 new Thread("ThreadB"){ @Override public void run() { super.run(); System.out.println("ThreadB:"+local.get()); } }.start(); } 复制代码
看一下打印的结果:
System.out: main:我是main System.out: ThreadA:我是ThreadA System.out: ThreadB:null 复制代码
从结果来分析,上面那段注释就很好理解了,main、threadA、threadB 三个线程中都拥有一个不同的副本,可以通过set和get方法对本线程中的变量进行操作,并不会影响到其他线程的变量;
我们从源码里面来分析一下:
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { //如果k==null,删除value的引用,防止泄露 replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } 复制代码
ThreadLocalMap 的源码并不复杂,我这里只看一些比较重要的方法,省略一些无用的代码,从上述代码可以看出来threadLocalMap用Entry类来进行存储,以ThreadLocal 作为key;在通过set方法或者构造方法进行赋值储存。在set方法里面每次调用都会检查一遍数据,并且会清除key为null的value
注意:ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,但是ThreadLocalMap的生命周期却和Thread一样长,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。在set方法里面每次调用都会检查一遍,并且会清除key为null的value,但是最后一次set的值始终会存在,被回收的时候还是有泄露的可能,如果想避免此处泄露,就需要手动调用ThreadLocalMap的remove()方法
构造方法里面并没有进行任何操作,我们直接从set方法开始:
public void set(T value) { Thread t = Thread.currentThread(); //获取到当前线程中维护的ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) //以ThreadLocal为key将键值对进行储存 map.set(this, value); else //初始化并储存value createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } 复制代码
set方法很简单,获取到当前线程中维护的 ThreadLocalMap,如果尚未初始化,就调用createMap()方法来进行初始化,并以当前的threadLocal为key进行储存
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(); } 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; } 复制代码
可以看出来,get方法也是非常简单的,仅仅是从ThreadLocalMap中取出当前线程的数据,并且在未进行初始化的时候,初始化ThreadLocalMap;setInitialValue方法则是初始化map和设置默认值的方法,可以看到,getMap获取到的 ThreadLocalMap 为空,或者当前线程尚未赋值的时候,会在setInitialValue方法中通过initialValue()获取到默认值,initialValue()默认值为null,我们可以通过重写该方法来修改默认值;