简介
ThreadLocal可以理解为线程局部变量thread-local variables
ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是互相独立的。通过ThreadLocal的get和set方法就可以得到当前线程对应的值,而不会影响到其他线程的值
使用
可以看出两个线程操作的都是同一个ThreadLocal变量localVar,但是不同线程保存的值却是不同的,并且不会相互影响
package test;
public class ThreadLocalTest {
static ThreadLocal<String> localVar = new ThreadLocal<>();
static void print(String str) {
//打印当前线程中本地内存中本地变量的值
System.out.println(str + " :" + localVar.get());
//清除本地内存中的本地变量
localVar.remove();
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//设置线程1中本地变量的值
localVar.set("localVar1");
//调用打印方法
print("thread1");
//打印本地变量
System.out.println("after remove : " + localVar.get());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//设置线程1中本地变量的值
localVar.set("localVar2");
//调用打印方法
print("thread2");
//打印本地变量
System.out.println("after remove : " + localVar.get());
}
});
t1.start();
t2.start();
}
}
重点类或变量
ThreadLocalMap
查看ThreadLocal的源码可以发现ThreadLocal中有一个静态内部类ThreadLocalMap,这个Map可以类比我们常用的HashMap,只是这里解决哈希冲突采用的方法是线性探测法。table同样是一个Entry数组,Map的key是ThreadLocal,value是我们保存的变量
static class ThreadLocalMap {
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
//ThreadLocalMap构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//内部成员数组,INITIAL_CAPACITY值为16的常量
table = new Entry[INITIAL_CAPACITY];
//位运算,结果与取模相同,计算出需要存放的位置
//threadLocalHashCode比较有趣
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//Entry为ThreadLocalMap静态内部类,对ThreadLocal的弱引用
//同时让ThreadLocal和储值形成key-value的关系
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
Thread
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
看到这里我们就很容易明白线程是如何维护各自变量的,其实就是通过Thread类的ThreadLocalMap类型变量threadLocals来维护,Map的key是ThreadLocal,value是我们保存的变量
重点方法
ThreadLocal.get
获取当前线程的ThreadLocalMap,然后以自身作为key去寻找value
public T get() {
//(1)获取当前线程
Thread t = Thread.currentThread();
//(2)获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
//(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
return setInitialValue();
}
private T setInitialValue() {
//protected T initialValue() {return null;}
T value = initialValue();
//获取当前线程
Thread t = Thread.currentThread();
//以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
//如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
if (map != null)
map.set(this, value);
//如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
return value;
}
//ThreadLocalMap中getEntry方法
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
ThreadLocal.set
获取当前线程的ThreadLocalMap,如果不存在则先创建,否则直接放进Map
public void set(T value) {
//(1)获取当前线程(调用者线程)
Thread t = Thread.currentThread();
//(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
//(3)如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
if (map != null)
map.set(this, value);
//(4)如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
}
ThreadLocal.remove
remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量
public void remove() {
//获取当前线程绑定的threadLocals
ThreadLocalMap m = getMap(Thread.currentThread());
//如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
if (m != null)
m.remove(this);
}
哈希冲突
ThreadLocalMap既然是类似HashMap的结构,那就一定存在哈希冲突,ThreadLocalMap采用线性探测法来解决哈希冲突
线性探测法:把table看成一个环,假设当前table长度为16,也就是说如果计算出来key的hash值为14,如果table[14]上已经有值,并且其key与当前key不一致,那么就发生了hash冲突,这个时候将14加1得到15,取table[15]进行判断,这个时候如果还是冲突会回到0,取table[0],以此类推,直到可以插入
弱引用
ThreadLocal在用作ThreadLocalMap的key时,是被设计为弱引用的。
ThreadLocalMap的内部类Entry被设计为实现了WeakReference,Entry用来存放数据。在构造Entry对象时,将传进来的ThreadLocal对象包装成了真正的弱引用对象,而Entry对象和内部的value对象本身是强引用的。
弱引用的解释:
只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
简单理解就是当垃圾回收时,该对象只被WeakReference对象的弱引用字段(T reference)所引用,而未被任何强类型的对象引用,那么,该弱引用的对象就会被回收。
Entry.key为什么采用弱引用
为了让程序自动的对访问不到的数据进行回收提醒
内存泄露
如图所示,当我们把ThreadLocalRef设置为null时,由于Entry中的key由于对ThreadLocal是是弱引用,此时ThreadLocal会被GC回收,我们在ThreadLocalMap不可能再访问到这个key对应的value,但这个value此时还存在当前线程对应的强引用,假如这个线程是线程池中的对象,一直不被访问的话,value将永远无法被GC回收,造成内存泄露
解决:ThreadLocal使用完毕及时调用remove方法
内部优化:调用ThreadLocal的get/set方法时,在发生哈希冲突的情况是可能自动帮我们清除key为null的Entry的,但还是手动调用remove方法最好
InheritableThreadLocal
ThreadLocal不支持继承性:同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的,如果需要这个功能可以使用InheritableThreadLocal
InheritableThreadLocal 主线程在创建新线程的时候能够使新线程继承主线程的InheritableThreadLocal 内保存的值。
ThreadLocalMap中的构造函数会将父线程的inheritableThreadLocals中的变量复制一份到子线程的inheritableThreadLocals变量中
TransmittableThreadLocal
github地址:https://github.com/alibaba/transmittable-thread-local
JDK
的InheritableThreadLocal
类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal
值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal
值传递到 任务执行时
ThreadLocal的需求场景即TransmittableThreadLocal的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的执行组件情况下传递ThreadLocal值』则是TransmittableThreadLocal目标场景