ThreadLocal简介
-
ThreadLocal类为线程提供一份独立的本地变量,这些本地变量可以通过set(),get()操作
-
线程Thread对象中有一个ThreadLocalMap,这个Map的key就是ThreadLocal对象,value就是本地变量
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
-
当我们使用ThreadLocal的set()方法时,实际上就是把当前ThreadLocal对象和本地变量放入线程的ThreadLocalMap中
-
当我们使用ThreadLocal的get()方法时,实际上就是从线程的ThreadLocalMap中,取出以当前ThreadLocal对象为key的value值
-
ThreadLocal对象和本地变量应该放入ThreadLocalMap哪个位置?是通过ThreadLocal对象的threadLocalHashCode计算的
-
threadLocalHashCode这个值在每次创建ThreadLocal对象时都会增加一个哈希增量,降低碰撞概率
//通过nextHashCode()计算
private final int threadLocalHashCode = nextHashCode();
//保证原子性更新
private static AtomicInteger nextHashCode = new AtomicInteger();
//HASH_INCREMENT:哈希增量
private static final int HASH_INCREMENT = 0x61c88647;
//每创建一个ThreadLocal对象,nextHashCode这个值就会增长0x61c88647
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
ThreadLocalMap属性
-
ThreadLocalMap是一个哈希表,key保存的是ThreadLocal对象,value保存的本地变量
-
key为什么使用弱引用?
-
当ThreadLocal对象失去强引用,由于key是弱引用此时只要有GC就会回收ThreadLocal对象
-
哈希表中的与ThreadLocal对象相关联的Entry再次去entry.get()时,此时拿到的key是null
站在ThreadLocalMap的角度就可以分辨出哪些Entry是过期的,哪些Entry是非过期的 -
ThreadLocalMap会清理过期的Entry
-
ThreadLocalMap发生哈希冲突时,采用线性探测法:从冲突位置一直向后找一个可用位置
//哈希表结点
static class Entry extends WeakReference> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
//初始化的哈希表长度:16,必须是2的次方
private static final int INITIAL_CAPACITY = 16;
//哈希表
private Entry[] table;
//哈希表存放的结点个数
private int size = 0;
//扩容阈值
private int threshold;
//设置阈值为长度的三分之二
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
//返回上一个位置
private static int prevIndex(int i, int len) {
//如果小于0,就回到最后
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
//返回下一个位置
private static int nextIndex(int i, int len) {
//如果超过长度从0开始
return ((i + 1 < len) ? i + 1 : 0);
}
//只有在线程第一次存储本地变量时才会创建ThreadLocalMap对象
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
//创建哈希表长度为16
table = new Entry[INITIAL_CAPACITY];
//找到key对应的哈希表中的位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//创建Entry对象存放到指定哈希表的指定位置
table[i] = new Entry(firstKey, firstValue);
//此时哈希表中只有一个结点
size = 1;
//设置扩容阈值:哈希表长度的三分之二
setThreshold(INITIAL_CAPACITY);
}
解析ThreadLocalMap的getEntry()和getEntryAfterMiss()
-
getEntry()方法是ThreadLocalMap用来根据key获取value的
-
getEntryAfterMiss()方法会继续向当前位置后面搜索 e.key == key 的Entry
private Entry getEntry(ThreadLocal key) {
//计算key对应的哈希表位置
int i = key.threadLocalHashCode & (table.length - 1);
//访问哈希表中指定位置的Entry
Entry e = table[i];
//条件一:e有值
//条件二:e的key与当前查询的key一致
if (e != null && e.get() == key)
return e;
else
//此时e为空,或者e的key不是我们要找的key
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
//当前哈希表
Entry[] tab = table;
//哈希表长度
int len = tab.length;
//e:循环处理的结点
while (e != null) {
//当前位置中Entry对象的key
ThreadLocal k = e.get();
//找到了
if (k == key)
return e;
//ThreadLocal对象已经被GC回收了,因为key 是弱引用
if (k == null)
//删去过期数据,具体后面会写
expungeStaleEntry(i);
else
//更新index,继续向后搜索
i = nextIndex(i, len);
//获取下一个Entry结点
e = tab[i];
}
//没找到
return null;
}
解析ThreadLocalMap的set()
- set()方法是把key-value键值对放入ThreadLocalMap,如果已经有这个key就是替换,否则就是增加
private void set(ThreadLocal key, Object value) {
//哈希表
Entry[] tab = table;
//哈希表长度
int len = tab.length;
//计算当前key在哈希表中的对应的位置
int i = key.threadLocalHashCode & (len-1);
//线性探测:找到key可以使用的位置,或者找到已经存在的key进行替换
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
//获取当前元素key
ThreadLocal k = e.get();
//已经存在key,当前set操作是一个替换操作。
if (k == key) {
//做替换操作
e.value = value;
return;
}
//向后寻找过程中key == null,说明当前Entry是过期数据
if (k == null) {
//这个位置的Entry已经过期,使用这个位置,有可能是替换或者增加,具体后面会写
replaceStaleEntry(key, value, i);
return;
}
}
//找到了可以使用的位置,创建一个新的Entry对象
tab[i] = new Entry(key, value);
//新添加,所以size + 1
int sz = ++size;
//进行一次启发式清理,具体后面会写
//条件一:启发式清理工作未清理到任何数据
//条件二:当前哈希表内的结点数目已经达到扩容阈值
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//这个方法后面会写
rehash();
}
解析ThreadLocalMap的replaceStaleEntry()
-
当set()方法放置key-value键值对时,线性探测到一个过期数据的位置,使用这个位置进行set()操作
//staleSlot这个位置上的结点是过期结点 private void replaceStaleEntry(ThreadLocal key, Object value, int staleSlot) { //哈希表 Entry[] tab = table; //哈希表长度 int len = tab.length; //临时变量 Entry e; //开始探测式清理过期数据的位置 //关于探测式清理expungeStaleEntry(),后面会写 int slotToExpunge = staleSlot; //以当前staleSlot向前查找,找过期的数据 for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)){ //向前找到了过期数据,更新探测清理过期数据的位置为i if (e.get() == null){ slotToExpunge = i; } } //为什么还要向后查找,不直接使用过期位置staleSlot呢? //因为相同的key都是连在一起的,还需要向后查找是不是有这个key //以当前staleSlot向后查找,有可能是替换或者增加 for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { //获取当前结点key ThreadLocal k = e.get(); //找到了key,替换 if (k == key) { //替换新数据 e.value = value; //下面这两步操作实际上是交换了staleSlot位置和i位置的数据 //这是因为staleSlot位置是过期数据,把我们未过期的数据放到那,减少了get()操作是向后线性探测的时间 tab[i] = tab[staleSlot]; tab[staleSlot] = e; // 前面的向前查找过期数据,并未找到过期的Entry // 下面的向后查找过期数据,也未找到过期的Entry if (slotToExpunge == staleSlot) //当前位置i存放的就是过期数据,更新探测清理过期数据的位置为i slotToExpunge = i; //cleanSomeSlots:启发式清理,具体后面会写 //探测式清理expungeStaleEntry():slotToExpunge是过期数据的位置 //expungeStaleEntry():会从slotToExpunge位置开始向后清理过期数据,直到未过期的位置 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } //条件一:当前遍历的Entry是一个过期数据 //条件二:前面的向前查找过期数据,并未找到过期的Entry if (k == null && slotToExpunge == staleSlot) //更新探测清理过期数据的位置为当前位置 slotToExpunge = i; } //什么时候执行到这里呢? //并未发现 k == key 的Entry,可以使用过期位置staleSlot //直接将新数据添加到staleSlot这个位置 tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); //前面查找到了过期的Entry,开启清理数据 if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }
解析ThreadLocalMap的expungeStaleEntry()
-
expungeStaleEntry()用来探测过期数据并清除,从staleSlot位置开始,一直探测到Entry为null的位置
-
探测到非过期数据时,会把这个结点放到更正确的位置
private int expungeStaleEntry(int staleSlot) {
//哈希表
Entry[] tab = table;
//哈希表长度
int len = tab.length;
//清除当前位置value
tab[staleSlot].value = null;
//因为staleSlot位置的Entry是过期的,直接清除
tab[staleSlot] = null;
//上面Entry被清空了
size--;
//e:表示当前遍历结点
Entry e;
//i:表示当前遍历的位置
int i;
//for循环从 staleSlot + 1的位置开始探测过期数据,直到碰到Entry为空结点。
for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
//获取当前遍历结点Entry的key
ThreadLocal k = e.get();
//k表示的ThreadLocal对象已经被GC回收了,当前Entry过期
if (k == null) {
//清除当前位置value
e.value = null;
//过期了,清除
tab[i] = null;
//上面Entry被清空了
size--;
} else {
//当前遍历的位置中Entry是非过期数据,下面会把当前遍历位置的Entry放到更合适的位置
//重新计算当前Entry对应的位置
int h = k.threadLocalHashCode & (len - 1);
//当前Entry存储时,发生过hash冲突,被向后偏移过了
if (h != i) {
//将当前位置设为null
tab[i] = null;
//以正确位置h开始,查找第一个可以存放Entry的位置
while (tab[h] != null)
h = nextIndex(h, len);
//将当前结点放入到距离正确位置最近的位置
tab[h] = e;
}
}
}
//返回的位置为空结点
return i;
}
解析ThreadLocalMap的cleanSomeSlots()
-
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len)中expungeStaleEntry(slotToExpunge)是Entry为空的位置,len是长度
-
cleanSomeSlots(i, sz)中i是被新添加的Entry位置,sz为哈希表Entry数目
private boolean cleanSomeSlots(int i, int n) {
//是否清除过过期数据
boolean removed = false;
//哈希表
Entry[] tab = table;
//哈希表长度
int len = tab.length;
do {
//这里为什么不是从i就检查呢?
//因为传进来i这个位置Entry为空,或者是新添加的,不是过期数据
//下面循环中i也是探测清理返回的Entry为空的位置
//获取当前i的下一个
i = nextIndex(i, len);
//获取哈希表中i位置的Entry
Entry e = tab[i];
//条件一:Entry不为空
//条件二:Entry是一个过期的数据
if (e != null && e.get() == null) {
//重新更新n为哈希表长度
n = len;
//表示清理过数据
removed = true;
//以当前过期的Entry为开始做一次探测式清理
i = expungeStaleEntry(i);
}
//n无符号右移
} while ( (n >>>= 1) != 0);
return removed;
}
解析ThreadLocalMap的remove()
- remove()方法是清除key所对应的Entry
private void remove(ThreadLocal key) {
//哈希表
Entry[] tab = table;
//哈希表长度
int len = tab.length;
//计算当前key在哈希表中的对应的位置
int i = key.threadLocalHashCode & (len-1);
//线性探测:从当前位置向后查找key
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
//找到了
if (e.get() == key) {
//清除key
e.clear();
//探测式清除
expungeStaleEntry(i);
return;
}
}
}
解析ThreadLocalMap的rehash()和resize()
-
rehash()方法会清除所有的过期Entry,清理完后根据阈值判断是否扩容
-
resize()方法就是扩容,迁移所有的Entry
private void rehash() {
//清除所有过期Entry,下面具体写这个方法
expungeStaleEntries();
//清理完过期数据后,当前散列表内的Entry数量仍然达到了扩容阈值的四分之三
if (size >= threshold - threshold / 4)
//扩容
resize();
}
private void expungeStaleEntries() {
//哈希表
Entry[] tab = table;
//哈希表长度
int len = tab.length;
//遍历整个哈希表
for (int j = 0; j < len; j++) {
//当前的Entry结点
Entry e = tab[j];
//结点已过期
if (e != null && e.get() == null)
//探测式清除
expungeStaleEntry(j);
}
}
//扩容
private void resize() {
//旧的哈希表
Entry[] oldTab = table;
//旧的哈希表长度
int oldLen = oldTab.length;
//扩容后的表长度
int newLen = oldLen * 2;
//新的哈希表
Entry[] newTab = new Entry[newLen];
//表示新哈希表中的Entry数量
int count = 0;
//遍历旧表迁移数据到新表
for (int j = 0; j < oldLen; ++j) {
//旧表Entry结点
Entry e = oldTab[j];
//旧表Entry结点中有数据
if (e != null) {
//旧表Entry结点中的key
ThreadLocal k = e.get();
//旧表Entry结是一个过期数据
if (k == null) {
e.value = null; // Help the GC
} else {
//计算出当前Entry在扩容后的新表的存储
int h = k.threadLocalHashCode & (newLen - 1);
//找到一个距离h最近的一个可以使用的位置
while (newTab[h] != null)
h = nextIndex(h, newLen);
//将数据存放到新表的位置中
newTab[h] = e;
//数量+1
count++;
}
}
}
//设置下一次触发扩容的指标
setThreshold(newLen);
size = count;
//设置哈希表为新表
table = newTab;
}
解析ThreadLocal的set()
-
set()方法用来设置与当前线程关联的本地变量
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对象,需要为当前线程创建ThreadLocalMap createMap(t, value); } ThreadLocalMap getMap(Thread t) { //返回当前线程的threadLocals return t.threadLocals; } void createMap(Thread t, T firstValue) { //创建一个ThreadLocalMap对象给当前线程 t.threadLocals = new ThreadLocalMap(this, firstValue); }
解析ThreadLocal的get()和setInitialValue()
- get()方法用来获取与当前线程关联的本地变量
public T get() {
//当前线程
Thread t = Thread.currentThread();
//当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//当前线程已经拥有自己的ThreadLocalMap
if (map != null) {
//调用ThreadLocalMap.getEntry方法拿到存放当前ThreadLocal对象的Entry,上面写过了
ThreadLocalMap.Entry e = map.getEntry(this);
//Entry不为空
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
//返回value
return result;
}
}
//当前线程没有有自己的ThreadLocalMap,或者该Map中没有存放当前ThreadLocal对象的Entry
//下面具体写这个方法
return setInitialValue();
}
-
setInitialValue()用来设置当前ThreadLocal对象与当前线程相关联的本地变量
-
这个本地变量是在initialValue()方法中初始化的,一般会重写这个方法
private T setInitialValue() {
//初始化本地变量
T value = initialValue();
//当前线程
Thread t = Thread.currentThread();
//当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//当前线程已经拥有自己的ThreadLocalMap
if (map != null)
//保存当前ThreadLocal和上面初始化的本地变量
map.set(this, value);
else
//当前线程还未创建ThreadLocalMap对象,需要为当前线程创建ThreadLocalMap
createMap(t, value);
//初始化的本地变量
return value;
}
//默认返回null,一般会重写这个方法
protected T initialValue() {
return null;
}
解析ThreadLocal的remove()
- remove()方法用来移除与当前线程关联的本地变量
public void remove() {
//当前线程的ThreadLocalMap对象
ThreadLocalMap m = getMap(Thread.currentThread());
//当前线程已经拥有自己的ThreadLocalMap
if (m != null)
////调用ThreadLocalMap.remove方法移除当前ThreadLocal对象的Entry,上面写过了
m.remove(this);
}