概述:
threadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据,对其他线程而言是隔离的(在每个线程的set()方法之后,只能get到本线程的变量的副本,对其余的线程是不可见的)Thread为每一个变量在线程中都创建了副本,每一个线程都可以访问自己的内部变量副本,就ThreadLocal本身而言,他是不存储值的,存储在ThreadLocalMap中
实际上,ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。同一种类型的数据只有一个ThreadLocal,而每一个线程都有一个ThreadLocalMap没一个线程都会持有一个ThreadLocalMap对象的变量threadLocals。Entry是一个弱引用,当为null的时候或者内存不足的时候,就会被回收掉。Entry的key是ThreadLocal(计算索引)
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;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
在实例化ThreadLocalMap的时候,会创建一个长度为16的Entry数组。通过hashCode与length位运算确定出一个索引值i[i = key.threadLocalHashCode & (len-1)进行位运算],这个i就是被存储在table数组中的位置。
//ThreadLocal中threadLocalHashCode相关代码.
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
//自增
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
所以说,因为static的原因,在每次new ThreadLocal
时因为threadLocalHashCode的初始化,会使threadLocalHashCode值自增一次,增量为0x61c88647。而0x61c88647为斐波那契散列乘数,通过它hash出来的结果分布会更加的均匀
方法:
set():
//set 方法
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//实际存储的数据结构类型
ThreadLocalMap map = getMap(t);
//如果存在map就直接set,没有则创建map并set
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//getMap方法
ThreadLocalMap getMap(Thread t) {
//thred中维护了一个ThreadLocalMap
return t.threadLocals;
}
//createMap
void createMap(Thread t, T firstValue) {
//实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
所以说,每个线程持有一个ThreadLocalMap对象。每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。
get():
//ThreadLocal中get方法
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();
}
//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来讲,他的索引值i是确定的,在不同线程之间访问时访问的是不同的table数组的同一位置即都为table[i],只不过这个不同线程之间的table是独立的。
- 对于同一线程的不同ThreadLocal来讲,这些ThreadLocal实例共享一个table数组,然后每个ThreadLocal实例在table中的索引i是不同的。
ThreadLocal的用途:
- 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
- 线程间数据隔离
- 进行事务操作,用于存储线程事务信息。
- 数据库连接,Session会话管理。
注:
1、如果突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。
2、在使用额过程中,一定要先set()在进行get(),否则会爆出空指针异常。
于2020/09/30完成
参考链接:
https://www.jianshu.com/p/3c5d7f09dfbd
https://baijiahao.baidu.com/s?id=1653790035315010634
https://blog.youkuaiyun.com/zy1994hyq/article/details/83310049