Thread与ThreadLocal
在Thread类中有一个ThreadLocalMap
类型成员变量,存储当前线程中的threadLocal变量。
ThreadLocal.ThreadLocalMap threadLocals = null;
一般情况下它初始化为null,只有在配置时才会进行构造
对于这个成员变量,在ThreadLocal
类中提供了一个getMap方法,用于获取线程中的threadLocals变量。
TheadLocalMap
构造方法
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);
}
ThreadLocalMap构造一个Entry数组,通过哈希计算的方法将key和value分配到对应位置的entry中。entry实现自弱引用。使用弱引用的目的是:用户只是new了一个ThreadLocal对象,所以当用户定义的ThreadLocal对象不再使用之后,ThreadLocal对象及其指向的T对象都应该可以被回收。
set方法
ThreadLocalMap的set方法是比较复杂的。
private void set(ThreadLocal<?> key, Object value) {
//Entry数组
Entry[] tab = table;
//Entry数组的长度
int len = tab.length;
//获取数组的下标
int i = key.threadLocalHashCode & (len-1);
//进入循环,循环条件是tab[i]对于的map非空,遇空停止
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//通过value值来找对应的key值
ThreadLocal<?> k = e.get();
//如果k已经有了,则直接覆盖,返回
if (k == key) {
e.value = value;
return;
}
// 如果k为null,则说明这个treadLocal变量被gc回收,这个Entry就是StaleEntry(脏Entry),要进行清理
if (k == null) {
//清理脏entry
replaceStaleEntry(key, value, i);
return;
}
}
// 找到空位置时,new一个entry存key、value
tab[i] = new Entry(key, value);
//size初始为0,set一个就加1.
int sz = ++size;
//清理其余过期对象,若sz大于threshold(阈值就是长度的三分之二),则需要扩容,重新hash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
set方法的关键点如下:
通过哈希计算entry数组的对应下标,从该下标开始循环。
循环过程中,遇到非空的,取该位置上的entrykey,看看是不是自己这个threadLocal变量,如果是,取对应位置的值。如果值为null说明已经被gc了,这个entry就是个脏entry,需要清理掉再配置值。如果非null,则直接覆盖,因此这个线程重新配置threadLocal时,不会有遗留。值set完后,直接return不继续执行
如果遍历非空的位置一直找不到合适的,会遍历直到一个空位停下,创建一个新entry,然后再清理剩下的过期对象。
ThreadLocal
set方法
构造一个ThreadLocal变量的时候,一般使用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方法的思路如下:
获取当前线程
获取线程的threadLocalMap对象
如果map非空,调用ThreadLocalMap::set方法配置值
如果map为空,调用createMap方法构造新map
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();
}
可见get方法思路如下:
获取当前线程的threadLocalMap成员变量map
如果map非空,获取对应的entry,取值返回
如果map为空,调用setInitialValue方法把值配置进去
在setInitialValue方法中可以发现与set方法一致,只是多了一个调用initialValue赋值给value的过程,而initialValue不重写就是null。这个思路完成了一个get不到转而初始化的逻辑。
createMap方法
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
直接调用ThreadLocalMap的构造函数构造ThreadLocalMap对象,赋值给线程的成员变量。
getMap方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
直接返回线程的threadLocalMap变量
remove方法
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
remove方法就是计算threadLocal变量对应的哈希位置,调用clrear方法进行清理,同时再清理一波其他的脏entry。
每个线程结束后执行remove的意义在于防止上一轮thread的threadlocal变量带到下一轮产生脏数据。不使用remove可能导致threadLocal对象所指向的T对象有潜在的强引用,导致threadLocal对象也没法被回收。
InheritableThreadLocal
InheritableThreadLocal是一种特殊的可继承的threadLocal,子线程将直接继承父线程的threadLocal值,案例如下:
static final InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) throws Exception{
threadLocal.set(1);
Thread t = new Thread(new Domino(), "d");
t.start();
TimeUnit.SECONDS.sleep(2);
}
static class Domino implements Runnable {
@Override
public void run() {
Integer i = threadLocal.get();
System.out.println(i);
}
}
案例中输出1,如果用普通的,输出就是null
评论区