ThreadLocal
jdk1.7之前,由ThreadLocal维护一个hashmap,每一个键值对的key 为 当前线程,value 为 分配到的‘’资源’‘
jdk1.8之前,由各自线程维护一个map,每一个键值对的key 为 threadlocal ,value 为 分配到的资源
这样做的优势是: map的生命周期就和线程一样,同时生成同时销毁 ;
map里面的key 是 一个弱引用 ,有利于GC回收,并且站在map的角度的话,能够知道哪个threadlocal是被回收了的
全局变量
//每创建一个ThreadLocal对象时, 会为每一个分配到资源的线程分配一个threadLocalHashCode值,
//每一个threadLocalHashCode值之间间隔一个黄金分割点。
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
get() 获取map的value值
获取每一个线程与之关联的threadLocal对象分配到的资源(变量),这个变量只有当前线程能够访问到
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
//然后通过this获取entryset,然后通过entryset获取到value(value是当前线程分配到的变量)
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;
}
}
//如果当前线程的map为空,或者当前线程分配到的value为空就进行初始化分配
return setInitialValue();
}
setInitialValue() 初始化值 or 初始化map
//如果当前线程的map不为空,就会在当前线程map对象的entry的value值里放置一个 刚刚threadlocal初始化的与当前线程相关联的变量
//如果当前线程的map为空,就创建一个map
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;
}
createMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set() 修改map的value值
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadLocalMap对象
ThreadLocalMap map = getMap(t);
//条件成立:说明当前线程的threadLocalMap已经初始化过了
if (map != null)
//调用threadLocalMap.set方法 进行重写 或者 添加。
map.set(this, value);
else
//执行到这里,说明当前线程还未创建 threadLocalMap对象。
//参数1:当前线程 参数2:线程与当前threadLocal相关的局部变量
createMap(t, value);
}
remove() 移除当前线程对应的value值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocal 的方法比较简单, 但是这些方法里面都离不开一个东西,就是ThreadLocalMap ,所有下面是ThreadLocalMap的探究
ThreadLocalMap
getEntry()
在ThreadLocal的get()方法中调用了这个方法,来通过threadlocal得到entry
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
//e == null 或者 key不相同(发生了hash冲突)
return getEntryAfterMiss(key, i, e);
}
getEntryAfterMiss()
从当前位置线性向后寻找key == this.key
→ 如果在这个过程找到就返回key
没有找到的话就继续向后寻找
并且在找的过程中如果遇到 key == null的情况,说明弱引用已经被回收,需要执行一次探测过期数据回收
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
expungeStaleEntry() 探测式清除逻辑
探测过期数据回收,如果当前key == null,就帮助GC回收,然后从下一个位置开始遍历整个数组帮助回收所有key == null的值
如果当前key != null,就去优化当前数据的位置**(** 因为当前位置的entry可能是hash冲突后,偏移到这里的,如果前面哈希冲突的entry有很多并且都给回收了的话,那么就需要优化一下当前entry的位置了。重新计算一下hash值,然后重新从hash值定位到的 i 出发,找到个合适的位置放下当前entry ) ,这样子做的话会使得查询性能更好,能使得entry离正确位置更近
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
//回收当前位置的entry
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
//遍历entry数组
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//回收数组中 key == null的值
if (k == null) {
e.value = null;
tab[i] = null;
size--;
}
//key != null, 优化一下当前entry的位置
else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
set()
ThreadLocal的set()方法调用了这个set()方法,如果是在for循环里面,执行的其实是替换逻辑,退出for循环,执行的就是set一个新值逻辑
set的过程中,如果 1. k == key,说明threadlocal已经存在,只需要将值替换就行了
- k == null, 说明当前位置的threadlocal已经没了,执行replaceStaleEntry()的逻辑
- 循环完都没有找到可以用的位置,就新建一个entry放在循环结束的位置
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();
//1.
if (k == key) {
e.value = value;
return;
}
//2.
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//3.
tab[i] = new Entry(key, value);
int sz = ++size;
//
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
replaceStaleEntry()
从key == null的位置出发,向后寻找k == key的位置,找到就交换下位置,找不到就在当前位置新建一个entry
然后用一个变量slotToExpunge来标记null,用于探测式清理
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
//这个参数是用于后面探测式清理的时候用的
int slotToExpunge = staleSlot;
清除 //向前遍历,如果找到threadlocal == null,就将slotToExpunge标记到那个位置
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
//向后遍历,替换key值
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//找到相同的key,实现替换逻辑
if (k == key) {
e.value = value;
//交换一下位置,将被替换key那个位置,与起始查找位置换一下,我觉得有利于查询
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
清除 //与清除逻辑有关
//如果在之前向前遍历的时候,slotToExpunge没有变化 --》 就将slotToExpunge设置为当前为null的位置i(因为交换过位 置了)
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
清除 //找的时候又找到一个null,这个也是与清除逻辑有关
//将slotToExpunge设置为当前为null的位置i
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
//如果向后遍历也没有找到相同的key值,就在进入这个方法找到null的那个位置,创建一个新的entry
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
清除 //这个也是清除逻辑
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
cleanSomeSlots 启发式清除逻辑
启发–》其实就是帮助探测式清除找到下一段要清除的一段(因为探测式清除,到entry==null的时候就会停止清除了)
每启发一次探测式清除,就会将n重新设置为Entry[] tab 的长度。
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
//这里设置长度
n = len;
removed = true;
//开启探测式清除
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}