作用
ThreadLocalMap作用是存储线程所有ThreadLocal与Value对应关系。
其中ThreadLocal作为Key,使用的是弱引用,如果程序中没有对这个ThreadLocal的引用,ThreadLocal会被GC,但是Entry依然存在,只有在特定动作下,这些过时的Entry才会被清除。
构造方法
- ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)
作用:构造一个以firstKey与firstValue为初始值的ThreadLocalMap。 - private ThreadLocalMap(ThreadLocalMap parentMap)
作用:通过把父ThreadLocalMap中的值复制过来,初始化一个ThreadLocalMap。
普通方法
- private Entry getEntry(ThreadLocal<?> key)
作用:得到与Key关联的Entry,如果没有直接命中,则使用getEntryAfterMiss方法。 - private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)
作用:当在它最近的哈希位置找不到Entry的时候,getEntry就会使用此方法,不断的向后查找 Entry,当发现后一个Entry失效,则会清除它,直到Entry为空或命重返回。 - private void set(ThreadLocal<?> key, Object value)
作用:设置与ThreadLocal相关的对象 - private void remove(ThreadLocal<?> key)
作用:通过Key去移除Entry - private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot)
作用:替换与ThreadLocal相关的对象到过时位置中去。 - private int expungeStaleEntry(int staleSlot)
作用:从staleSlot位置开始,清除过时的Entry,重新排序有效的Entry,直到遇到Entry为null。
返回:Entry为null时候,在table中的坐标。 - private boolean cleanSomeSlots(int i, int n)
作用:从i位置开始,最少运行log2(n)次,清理table中过期的Entry。
返回:如果有清理过Entry返回True,没有返回False。
注意:ThreadLocalMap在Thread中被声明,并且只在ThreadLocal中被调用。
以下又是枯燥的源码
/**
* ThreadLocalMap是一个自定义的散列映射仅适用于维护线程本地的值。
* 在ThreadLocal类之外不做任何动作。
* 该类是包私有的,允许Thread类中声明此域。
* 为了解决很大且长时间存留的使用,该散列表的Entry对key使用了弱引用。
* 然而,因为没有引用队列,当表空间不足的时候才会移除过时的Entry。
*/
static class ThreadLocalMap {
/**
* 这个散列映射里的Entry扩展了WeakReference,使用它的主引用域作为key
* (这里是ThreadLocal对象)。注意,空Keys(例如:Entry.get()==null)
* 意味着这个Key不再被引用,所以这个Entry会从table中删除。
* 这些Entries在下面的代码中被称为“过时Entry”。
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 与ThreadLocal关联的值 */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* 初始大小 -- 一定是二的幂。
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 在必要的时候,需要调整这个表的长度。
* 长度必须是二的幂。
*/
private Entry[] table;
/**
* 在这个Table里面,Entry的数量。
*/
private int size = 0;
/**
* 当达到这个值的时候,需要调整长度。
*/
private int threshold; // 默认是0
/**
* 设置阈值,当达到这个阈值的时候需要调整长度。
* 最坏的情况下,保留三分之二的负载系数。
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 增量i根据len取模。
*/
private static int nextIndex(int i, int len) {
//i+1小于len吗?
//true:返回i+1
//false:返回0
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
//i-1大于等于0吗?
//true:返回i-1
//false:返回len-1
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* 构建一个新的初始化Map,包含firstKey与firstValue。
* ThreadLocalMaps是懒加载创建的,所以当我们至少有一个Entry需要放到里面去的时候才创建。
* ,所以当我们至少有一个Entry需要放到里面去的时候才创建。
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化一个大小为INITIAL_CAPACITY的Entry数组
table = new Entry[INITIAL_CAPACITY];
//使用计算出来的i作为Entry的index
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//把Entry放入table中,index为i。
table[i] = new Entry(firstKey, firstValue);
//设置table大小为1
size = 1;
//设置重新调整大小的阈值
setThreshold(INITIAL_CAPACITY);
}
/**
* 构建一个新的ThreadLocalMap,其中包含所有父类ThreadLocalMap中有效的Entry。
* 仅由创建父类ThreadLocalMap调用。
*
* @param parentMap 这个Map与父线程关联
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
//先把父类的table拿出来
Entry[] parentTable = parentMap.table;
//获取父类Table的长度
int len = parentTable.length;
//以父类Table的长度设置阈值
setThreshold(len);
//以父类Table的长度创建一个新的Table
table = new Entry[len];
//迭代父类Table
for (int j = 0; j < len; j++) {
//根据index获取父类table里的Entry
Entry e = parentTable[j];
if (e != null) {
//获取Key,即ThreadLocal。
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
//获取这个ThreadLocal对应的Value
Object value = key.childValue(e.value);
//创建一个新的Entry,key与value与父类的Entry相同。
Entry c = new Entry(key, value);
//计算ThreadLocal的哈希值
int h = key.threadLocalHashCode & (len - 1);
//当这个哈希值在Table已经存在的时候,计算下一个Hash值。
while (table[h] != null)
h = nextIndex(h, len);
//新的Entry存入Table中
table[h] = c;
//size增加1
size++;
}
}
}
}
/**
* 得到与Key关联的Entry。这个方法本身只处理快速路径:
* 现有Key的直接命中。否则它将转发给getEntryAfterMiss。
* 这是被设计来发挥直接命中的最大性能,在某种程度上
* 通过这个方法更容易实现。
*
* @param key ThreadLocal对象
* @return entry 与Key关联的Entry。如果没有,就会返回null。
*/
private Entry getEntry(ThreadLocal<?> key) {
//计算index
int i = key.threadLocalHashCode & (table.length - 1);
//从table中拿出Entry
Entry e = table[i];
//如果Entry不为空,并且Entry的Key与param中的key相等。
if (e != null && e.get() == key)
//则返回这个Entry
return e;
else
return getEntryAfterMiss(key, i, e);//如果以上为False,则会调用此方法。
}
/**
* 当在它最近的哈希位置找不到Entry的时候,getEntry就会使用此方法。
*
* @param key ThreadLocal对象
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return entry 与Key关联的Entry。如果没有,就会返回null。
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
//把table赋值到tab
Entry[] tab = table;
//获取tab的长度
int len = tab.length;
//当Entry不等于空的时候
while (e != null) {
//把Entry的Key拿出来
ThreadLocal<?> k = e.get();
//如果Entry的Key等于参数中的key
if (k == key)
//直接返回Entry
return e;
//如果Entry中的Key为null
if (k == null)
//清除过时Entry
expungeStaleEntry(i);
else
//计算下一个index
i = nextIndex(i, len);
e = tab[i];
}
//最终返回null
return null;
}
/**
* 设置与ThreadLocal相关的对象
* 首先根据参数中的key,计算出在table中的hash坐标。
* 如果table的这个哈希坐标已经存在Entry了,就会有以下两种情况:
* 1、如果存在的Entry的Key等于参数中的key,直接把存在的Entry中的value替换成参数中的value,方法结束。
* 2、如果存在的Entry为过时的Entry,则会调用replaceStaleEntry方法,替换过时的Entry,方法结束。
* 如果table的这个hash坐标没有Entry,直接实例化一个新的Entry放进去。
* 最后去判断一下是否需要重新排列扩容一下table。
*
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
//我们没有使用像get()方法那样的快速路径,因为至少那是相同的,
//当使用set()去创建一个新的Entry与替换一个已经存在的Entry,
//在这种状况下,快速路径将会比不是快速路径更容易失败。
//把table赋值给tab
Entry[] tab = table;
//获取tab的长度
int len = tab.length;
//获取ThreadLocal的哈希坐标
int i = key.threadLocalHashCode & (len-1);
//首先从tab中拿出index为i的Entry,把值赋给e。
//如果e不等于空,则继续,直到e等于空。
//运行完一次之后,计算下一个i,并再次把tab中index为i的值赋给e
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//获取e的key
ThreadLocal<?> k = e.get();
//如果e的key与入参的key相同
if (k == key) {
//替换e的value
e.value = value;
//方法结束
return;
}
//如果e中的key等于空,说明这个Entry已经失效了
if (k == null) {
//把原本的Entry替换成新的Entry
replaceStaleEntry(key, value, i);
//方法结束
return;
}
}
//当发现table中,index为i的位置没有Entry
//就会把这个值设置在这里
tab[i] = new Entry(key, value);
//size自增
int sz = ++size;
//如果table中已经不存在过时的Entry了,并且Entry的个数已经大于等于阈值
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();//进行扩容并且重新哈希排序所有Entry
}
/**
* 通过Key去移除Entry
*/
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;
}
}
}
/**
* 将一个在一套操作中遇到的过时Entry替换为拥有特殊Key的Entry。
* 即使这个特殊Key的Entry已经存在,值参数中的value将会被储存进Entry。
*
* 作为副作用,这个方法会清除“run”中所有过时Entry
* (“run”是两个空位置之间,一连串的Entry。)
*
* @param key the key
* @param value 和这个Key关联的值
* @param staleSlot index of the first stale entry encountered while
* searching for key.
*/
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
//不断地找前一个Entry,直到前一个Entry为空。
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)//如果前一个Entry失效了
slotToExpunge = i;//把slotToExpunge替换为前一个Entry的index
//不断地找后一个Entry,直到后一个Entry为空。
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//如果参数中的key与后一个Entry中的key相等
//表明拥有参数中key的Entry已经在table里面了
if (k == key) {
//现在失效的Entry要被已经存在的Entry替换
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
//如果要被清除的位置与过时的位置相等
if (slotToExpunge == staleSlot)
slotToExpunge = i;//过时的位置替换为后一个Entry的位置
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// 如果我们在反向扫描中没有发现过期条目,
// 则在我们扫描key时发现的第一个过期Entry,就是第一个现在依然在“run”中的。
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 如果key找不到,放入一个新的Entry在过时位置
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 如果这里有任何其它过时的Entry在“run”中,清除他们。
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
/**
* 这个方法首先会把过时的位置清除了,然后会不断地计算出下一个index,
* 如果新的index拿到的Entry也是过时的,就会继续清除
* 如果新的index拿到的Entry还是有效的,则会看看index有没有因为扩容导致不正确了,如果不正确则重新计算搬移。
* 直到找到一个null,并且返回null在table中的index。
*
* @param staleSlot 已知具有空Key的位置索引
* @return 在过时位置后,下一个空位置的索引。
* (在过时位置与这个位置之间的所有值已经被检查是否删除)
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//清除过时位置的Entry
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
//重新散列直到遇到空值
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
//获得Entry的Key。
ThreadLocal<?> k = e.get();
//如果Entry的Key为空,表明已经过时。
if (k == null) {
//同样清除这个位置的Entry
e.value = null;
tab[i] = null;
size--;
} else {
//根据现在table的len重新哈希index
int h = k.threadLocalHashCode & (len - 1);
//如果旧的index与新的index不一样
if (h != i) {
//把旧的index存在的Entry清空
tab[i] = null;
//不像Knuth 6.4 Algorithm R中说的,
//我们一定要扫描直到空值,因为多个条目可能已经过时。
while (tab[h] != null)//当新的位置已经有Entry了
//计算下一个index
h = nextIndex(h, len);
//把旧的有效的Entry放到新的位置
tab[h] = e;
}
}
}
//直到遇到null,返回index。
return i;
}
/**
* 为了找出过期的Entry试探性地扫描table中的一些位置
* 当新的Element被添加,或者其它过期元素被清除的时候,这个方法会被调用。
* 它执行了一个对数次数的扫描,作为不扫描与和Element数量成比例扫描之间的平衡,
* 这会找到所有垃圾,但会导致一些插入使用了O(n)的时间
*
* @param i 一个已知的没有持有过期Entry的位置,将会从i之后开始扫描。
*
* @param n 扫描控制:log2(n)个单元将会被扫描,除非一个过期的Entry被找出来,
* 在这种状况下,log2(table.length)-1个额外的单元会被扫描。
* 当这个方法在元素插入的时候被调用,这个参数n就是元素的个数,
* 但是当从replaceStaleEntry方法进入的时候,就是table的长度。
* (记住:所有这些是可以被或多或少的增加n来改变的,以此来替代仅仅使用直对数n,
* 但是这个版本是简单的、快速的,并且看起来工作的挺好。)
*
* @return true 如果任何过时条目已经被清除了。
*/
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
//运行log2(n)次,原来是这么玩。无符号右移一位。
while ((n >>>= 1) != 0){
//计算下一个index
i = nextIndex(i, len);
//获取table中下标为index的Entry
Entry e = tab[i];
//当发现是失效的Entry的时候。
if (e != null && e.get() == null) {
//把table的长度赋值给n
n = len;
//改变removed标志
removed = true;
//移除table中index为i的值,并且刷新从i开始,但不包括i的table
//最终返回遇到null的index
i = expungeStaleEntry(i);
}
}
//返回removed标志
return removed;
}
/**
* 重新打包 和/或 调整table大小。
* 首先扫描整个table,移除失效的Entry。
* 如果这个不能充分地缩小table的大小,请将表的大小增加一倍。
*/
private void rehash() {
//移除所有失效的Entry,调整有效的Entry。
expungeStaleEntries();
//使用较低的阈值进行倍增,以避免迟滞。
if (size >= threshold - threshold / 4)
resize();
}
/**
* 倍增table的容量
*/
private void resize() {
//旧的table
Entry[] oldTab = table;
//旧的长度
int oldLen = oldTab.length;
//新的长度为旧的长度两倍
int newLen = oldLen * 2;
//重新创建一个table,并且长度是原来的两倍。
Entry[] newTab = new Entry[newLen];
int count = 0;
//迭代旧的table
for (int j = 0; j < oldLen; ++j) {
//获得旧table中的Entry
Entry e = oldTab[j];
//如果Entry不为空
if (e != null) {
//获取这个Entry的Key(ThreadLocal)
ThreadLocal<?> k = e.get();
//如果这个Entry的Key为空,说明已经失效了。
if (k == null) {
//去掉value的引用,帮助垃圾回收。
e.value = null;
} else {//如果这个Entry的key不为空,说明有效。
//用新的table长度重新计算这个Entry的哈希值。
int h = k.threadLocalHashCode & (newLen - 1);
//如果新的index在新的table中已经占用了
while (newTab[h] != null)
h = nextIndex(h, newLen);//重新计算下一个index
//把这个有效的Entry放入新的table中
newTab[h] = e;
//计数加1
count++;
}
}
}
//使用新的长度重新计算阈值
setThreshold(newLen);
//以下两个就不说了。。。
size = count;
table = newTab;
}
/**
* 移除table里面所有失效的Entry
*/
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
//迭代table中所有的Entry
for (int j = 0; j < len; j++) {
Entry e = tab[j];
//发现失效,立刻移除。
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
}