前言
ThreadLocal看类名就是线程本地变量的意思。从使用上来说,如果定义了一个ThreadLocal,那么各个线程针对这个ThreadLocal进行get/set都是线程独立的,也就是说,是线程隔离的本地变量。
Thread类中有一个ThreadLocalMap类型的成员,是一种hashmap,它以ThreadLocal作为key。所以,通过Thread对象和ThreadLocal对象二者,才可以唯一确定到一个value上去。线程隔离的关键,正是因为这种对应关系用到了Thread对象。
例子:
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
ThreadLocalMap.Entry e = map.getEntry(this);
e.value
1、类定义
1.1、成员变量
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);
}
-
每个ThreadLocal对象都有一个int值的成员变量,作为它在ThreadLocalMaps里的哈希值。 -
第一次构造ThreadLocal对象时,它的哈希值为0。此后,每个新构造出来的ThreadLocal对象都新增一个模数0x61c88647。 -
使用了AtomicInteger,就算两个线程同时构造了ThreadLocal对象,也能保证这个int成员变量各自线程的不同。 -
模数0x61c88647在2的幂为容量的哈希表上,能够完美散列,没有一个元素会哈希冲突。
#2、方法解析
2.1、get方法解析
public T get() {
1、获取当前线程
Thread t = Thread.currentThread();
2、根据当前线程获取ThreadLocalMap变量
ThreadLocalMap map = getMap(t);
if (map != null) {
3、调用内部类ThreadLocalMap方法获取Entry,后续讲解
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
3.1、获取value的值
T result = (T)e.value;
return result;
}
}
4、设置初始值
return setInitialValue();
}
***********************************************************************
private T setInitialValue() {
1、根据SuppliedThreadLocal函数时接口获取值
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
2、设置初始值,后续讲解
map.set(this, value);
else
3、创建ThreadLocalMap
createMap(t, value);
return value;
}
***********************************************************************
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
-
刚开始就直接找到了调用当前函数的线程是哪个(Thread t = Thread.currentThread();),然后调用getMap,最后居然直接去取Thread对象的一个ThreadLocal.ThreadLocalMap类型的threadLocals成员. -
上面这点就直接解释了,为什么ThreadLocal可以实现线程隔离的线程私有变量。因为它把这个Map对象直接作为了Thread对象的成员,这样,每个运行的线程都对应到一个唯一的Thread对象,而每个Thread对象都保存着各自的ThreadLocal.ThreadLocalMap类型的成员变量。
2.2、set和remove方法解析
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
比较简单,不做解析,重点关注后续的ThreadLocalMap中的方法。
2.3、父子线程
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
3、ThreadLocalMap静态内部类解析
3.1、成员变量
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 nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
3.2、构造方法
3.2.1、构造方法一
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
1、初始化数组长度 16
table = new Entry[INITIAL_CAPACITY];
2、计算hash值取模存放在数组的位置,散列法
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
3、新建Entry
table[i] = new Entry(firstKey, firstValue);
4、设置数组中包含元素的个数为1
size = 1;
5、设置初始阈值 16*2/3
setThreshold(INITIAL_CAPACITY);
}
3.2.2、构造方法二
父线程Thread中的inheritableThreadLocals成员,赋值到子线程的inheritableThreadLocals。
private ThreadLocalMap(ThreadLocalMap parentMap) {
1、其实就是转移值,不做解析
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
3.3、getEntry方法解析
private Entry getEntry(ThreadLocal<?> key) {
1、根据当前key取模,计算下标位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
2、没找到key说明再存入的时候可能发生了hash冲突
return getEntryAfterMiss(key, i, e);
}
注意注释我写了,如果e(取模下标的entry)为null,调用下面getEntryAfterMiss函数将直接返回null。为啥这么自信呢,哈希值取模下标的entry没找到,就认为目标key肯定不在map里了。显然,ThreadLocalMap的函数逻辑是保证了这样一点:多个哈希值不同但取模下标相同的ThreadLocal,在你操作完毕后,或者说操作开始前,它们肯定从这个取模下标开始放置的(后面的由于冲突,会依次放在后面索引),在移除的时候后面的值会重新根据hash取模进行计算下标位置,所以说该下标位置不可能为null。
名词解释:
-
数组元素为null,就是null node -
数组元素不为null,但是key为null,为stale entry -
循环通常从一个索引向后搜索,直到遇到第一个null entry(一般是指循环处理前,就是null的entry),结束循环。将这个索引直到第一个null entry称为连续段。 重点: 取模之后的连续段若找不到key,则在其他位置一定也找不到该key,
3.3、getEntryAfterMiss方法解析
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
1、循环遍历,向后搜索连续段,之前set的时候也是set到第一个不为null的值,
所以只需要遍历到第一个为null的位置即可
while (e != null) {
ThreadLocal<?> k = e.get();
2、key相等,直接返回value
if (k == key)
return e;
3、key为null,说明该Entry是 stale node,需要清除
if (k == null)
expungeStaleEntry(i);
else
4、循环自增,前面解释过
i = nextIndex(i, len);
5、获取下一个i下标的Entry
e = tab[i];
}
return null;
}
3.3、expungeStaleEntry方法解析
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
1、当前位置直接设置的value和Entry直接设置为null,size--
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
2、遍历连续段,直到下一个为null的位置
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
2.1、key为null,说明该位置还是stale node 同上1位置处理
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
3、key不为null,重新计算下标位置,让key节点更靠近取模的位置
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
3.1、h!=i,说明node下标不在取模的位置上, tab[i]直接设置为null
tab[i] = null;
3.2、从h出发找到后面下一个为null的位置
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
4、返回连续段后为null位置的下标即 tab[i] == null
return i;
}
expungeStaleEntry函数,看名字就知道是用来清理stale entry的。而且get、set、remove、resize都可能调用到它,实际上它也确实是用来处理stale entry的主要函数。
3.4、set方法解析
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
1、遍历连续段,直到为null的位置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
2、找到相同的key,直接修改value
if (k == key) {
e.value = value;
return;
}
3、key为null,该节点为 stale节点
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
4、到这儿说明没有找到key的节点,插入到i的位置
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
replaceStaleEntry函数中,无论怎样,都会把传入的key和value塞到staleSlot所在的下标上去,只不过有两种情况都会达到这个目标,具体看注释。另外,该函数会把staleSlot参数所在的run里的所有stale entry都给清理掉。
3.5、replaceStaleEntry方法解析
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
1、从该位置进行删除statle node节点
int slotToExpunge = staleSlot;
2、向前遍历连续段,slotToExpunge为最前面statle节点的下标或自己本身
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
3、向后遍历连续段
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
3.1、找到了相同的key,交换i与staleSlot的元素
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
3.2、如果相同,说明连续段前面没有stale
若不相同有两种可能:
(1)slotToExpunge为最前面stale的下标
(2)slotToExpunge为staleSlot后面的第一个位置
if (slotToExpunge == staleSlot)
slotToExpunge = i;
3.3、清除stale节点
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
3.4、进入这儿,说明staleSlot前面没有stale节点,
则让slotToExpunge为i,且i为staleSlot后面的第一个位置,即staleSlot+1或0。
因为staleSlot一定不为null或stale node,所以使expungeStaleEntry直接忽略该节点
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
4、没有找到相同的key,说明key不存在在数组中,直接插入到staleSlot位置
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
5、解析同3.2,其实就是expungeStaleEntry方法的起始下标位置
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
主要做了三件事:
3.6、cleanSomeSlots方法解析
private boolean cleanSomeSlots(int i, int n) {
1、设置清空标志
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
2、循环清除,最少循环log2(n)次
do {
2.1、后面的下标位置i
i = nextIndex(i, len);
Entry e = tab[i];
2.2、如果e为stale node,则进行清除
if (e != null && e.get() == null) {
2.3、重新赋值n,如果循环了log2(n)都没有进入到该位置,也就结束循环了,所以最少循环log2(n)次
n = len;
removed = true;
2.4、清除stale,返回的i使为null的下标,所以i可能会跳跃
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0); 3、无符号右移,若不进入上述判断执行log2(n)次
4、返回清除标志
return removed;
}
3.7、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) {
1、找到key,先清除key,将节点变成stalenode
e.clear();
2、然后执行expungeStaleEntry
expungeStaleEntry(i);
return;
}
}
}
|