ThreadLocal
ThreadLocal源码:
ThreadLocal
类中首先定义了如下的属性:
// final类型常量表示当前ThreadLocal实例的哈希值,通过方法nextHashCode()计算得出;
private final int threadLocalHashCode = nextHashCode();
// 静态类型变量nextHashCode,为了原子操作整型类AtomicInteger类对象;
private static AtomicInteger nextHashCode = new AtomicInteger();
// 静态类型常量表示哈希值一次增长多少;
private static final int HASH_INCREMENT = 0x61c88647;
静态方法nextHashCode
源码如下:
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
根据以上代码可以推算出ThreadLocal
对象实例HashCode
的计算规则:
- 初始值为0;
- 每创建一个新的
ThreadLocal
对象,nextHashCode
增加0x61c88647
,同时新的对象的哈希值为nextHashCode
增加之前的值。
为什么不一次增加1,而是一次增长0x61c88647
?
0x61c88647
是一个魔数,对于长度为2的幂次的数组,生成的哈希值可以达到完美散列。可以写一个程序进行验证:
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadLocalReview {
private static final int HASH_INCREMENT = 0x61c88647;
private static final AtomicInteger hash = new AtomicInteger();
public static void main(String[] args) {
int n = 16;
while (n > 0) {
System.out.println(n);
boolean[] appeared = new boolean[n];
hash.set(0);
for (int i = 0; i < n; i++) {
int hashRes = hash.addAndGet(HASH_INCREMENT) & (n - 1);
if (appeared[hashRes]) System.out.println(hashRes);
appeared[hashRes] = true;
}
System.out.println("Appeared res:");
for (int i = 0; i < n; i++) {
if (!appeared[i]) System.out.println(i);
}
n <<= 1;
}
}
}
程序从
n
=
16
n=16
n=16开始到
n
=
2
31
n=2^{31}
n=231检测哈希碰撞,结果并没有发现某一个n
存在碰撞。
在ThreadLocal
对象的其他方法中,设置和获取值都是先获取当前线程的Thread
对象,接着从Thread
对象中获取到ThreadLocalMap
对象,接着对ThreadLocalMap
对象进行操作。以set()
方法为例:
public void set(T value) {
// 获取当前线程Thread对象
Thread t = Thread.currentThread();
// 从当前线程Thread对象中获取到ThreadLocalMap对象;
ThreadLocalMap map = getMap(t);
if (map != null) {
// map不为空的话就向map中添加键为当前ThreadLocal对象,值为value
map.set(this, value);
} else {
// 否则为当前线程创建一个新的ThreadLocalMap对象
createMap(t, value);
}
}
// ThreadLocalMap对象直接从当前线程的treadLocals属性中获取;
ThreadLocal.ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 为当前线程创建一个新的ThreadLocalMap对象,并在ThreadLocalMap对象中添加初始键值对;
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
}
ThreadLocalMap
ThreadLocalMap
对象有一个静态内部类Entry
用于存储键值对:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
该类继承自WeakReference
类,并用WeakReference
存储了ThreadLocal
对象。一旦线程Thread
对象被回收,ThreadLocal
对象就只有弱引用,那么ThreadLocal
对象在下一次垃圾回收之时必定会被回收。这时候Entry
对象中保存的键的值就会变为null
。
ThreadLocalMap
对象的属性有:
// 静态常量,表示Map的初始容量为16;
private static final int INITIAL_CAPACITY = 16;
// Entry数组
private ThreadLocal.ThreadLocalMap.Entry[] table;
// Map存储的键值对数量
private int size = 0;
// 进行resize时的阈值
private int threshold; // Default to 0
ThreadLocalMap
默认的构造方法为:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化Entry数组为初始容量大小
table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];
/// 计算初始键值对在数组中的位置,为ThreadLocal的哈希取最后4位
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
size = 1;
//设置阈值的大小;
setThreshold(INITIAL_CAPACITY);
}
// 可以看到阈值大小为长度的三分之二取整;
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
ThreadLocalMap
的set
方法为:
private void set(ThreadLocal<?> key, Object value) {
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
// 计算ThreadLocal对象在Entry数组中的位置;
int i = key.threadLocalHashCode & (len-1);
// 从下标i开始遍历tab数组;
for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 如果当前Entry数组的key和新增加的相等,则直接更新value;
if (k == key) {
e.value = value;
return;
}
// 如果key的值为null,说明ThreadLocal对象已经被回收,因此用新插入的键值对代替原有的键值对
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 循环结束后的i为新键值对的插入位置
tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
// 计算当前下标的下一个下标位置,为对下标进行循环;
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
ThreadLocal.ThreadLocalMap.Entry e;
int slotToExpunge = staleSlot;
// 从当前的插入位置开始前向遍历tab数组中不为空的元素,找到最开始key为null的entry;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// 从插入位置开始后向遍历
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 如果当前遍历的Entry中的键值为key
if (k == key) {
e.value = value;
// 交换插入位置和i的Entry
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 如果将要擦去的位置和插入位置相等,则将将要擦去的位置设置为i
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// 如果k是null而且将要擦去的位置和插入位置相等,则将将要擦去的位置设置为i
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
private int expungeStaleEntry(int staleSlot) {
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
// 清除tab数组当前位置的Entry;
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
ThreadLocal.ThreadLocalMap.Entry e;
int i;
// 从当前位置的下一个开始遍历直到碰到一个元素值为null为止
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 如果当前的key是null,则清除当前位置的entry
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// 否则为当前位置的Entry寻找新的插入位置;
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
// 最终返回第一个值为null的位置
return i;
}
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
ThreadLocal.ThreadLocalMap.Entry e = tab[i];
if (e != null && e.get() == null) {
// 如果发现了
n = len;
removed = true;
i = expungeStaleEntry(i);
}
//
} while ( (n >>>= 1) != 0);
return removed;
}
set
方法创建Entry
对象,并使用ThreadLocal
对象的threadLocalHashCode
计算哈希值,按理来说是不会产生冲突的,但是set
方法还是 检测到了冲突,并通过for
循环从插入位置找起直到第一个为null
的位置,或是key
为null
的位置作为冲突时新增Entry
时的插入位置,原因何在?
在set
方法中,如果发现了某一key
值为null
说明其已经被垃圾回收,则会调用replaceStaleEntry
方法清理Entry
数组中的key
为null
的Entry
。可以看到方法replaceStaleEntry
对从插入位置开始,先前向遍历寻找key
为null
的Entry
,接着进行后向遍历,结果有如下两种情况:
- 发现一个
key
和插入的key
相等,更新Entry
中value
的值并将其与插入位置中的Entry
互换,更新插入位置;之后调用expungeStaleEntry
方法从插入位置开始到第一个值为null
的元素,删除值为null
的Entry
,为值不为null
的Entry
重新寻找插入位置:int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; }
可以看到只有对哈希值与位置不一致的元素才会寻找新位置。新位置是从数组当前位置开始第一个元素值为
null
的位置。
之后调用cleanSomeSlots
方法。该方法以n
二进制长度为一个单位进行启发式搜索,如果发现有key
的值为null
则继续调用expungeStaleEntry
方法并重新开始计数。
2. 发现key
为null
且slotToExpunge
变量和插入位置相等时更新slotToExpunge
;最后调用expungeStaleEntry
和cleanSomeSlots
方法。
在set
方法的最后如果发现所有的key
为null
的Entry
元素已经被清理完了之后数组中元素的大小仍然大于阈值,则执行rehash
方法进行重新哈希。
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
private void resize() {
ThreadLocal.ThreadLocalMap.Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
ThreadLocal.ThreadLocalMap.Entry[] newTab = new ThreadLocal.ThreadLocalMap.Entry[newLen];
int count = 0;
for (ThreadLocal.ThreadLocalMap.Entry e : oldTab) {
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
rehash
方法首先调用expungeStaleEntries
方法清理tab
数组中的所有key
为null
的Entry
,接着判断size
是否超过了阈值的
3
4
\frac{3}{4}
43,超过的话才会进行rehash
。
getEntry
方法理解起来相对简单,源码如下:
private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
ThreadLocal.ThreadLocalMap.Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private ThreadLocal.ThreadLocalMap.Entry getEntryAfterMiss(ThreadLocal<?> key, int i, ThreadLocal.ThreadLocalMap.Entry e) {
ThreadLocal.ThreadLocalMap.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;
}
InheritableThreadLocal:
InheritableThreadLocal
为父线程和子线程之间提供了可继承的ThreadLocal
变量。Java
中父线程和子线程的定义为:如果线程A
创建了线程B
,则A
是B
的父线程,B
是A
的子线程。可以通过以下的方式使用InheritabeThreadLocal
对象:
public class ThreadLocalReview {
// 声明一个静态InheritableTreadLocal对象
static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
// 在主线程中设置值
inheritableThreadLocal.set("Parent thread's value");
// 在主线程中打印出值
System.out.println(Thread.currentThread().getName() + ' ' + inheritableThreadLocal.get());
Runnable runner = () -> {
Thread cur = Thread.currentThread();
// 在子线程中直接打印值
System.out.println(cur.getName() + ' ' + inheritableThreadLocal.get());
};
Thread childThread = new Thread(null, runner, "childThread", 1000, true);
childThread.setDaemon(true);
childThread.start();
}
}
程序的输出为:
main Parent thread's value
childThread Parent thread's value
可以看到在子线程中虽然没有设置InheritableThreadLocal
的值,但是在直接获取的时候还是获取到了父线程的值。
ThreadLocal
类的方法中有一childValue
方法,该方法会抛出UnsupportedOperationException
异常:
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
在ThreadLocalMap
中的某一构造方法中使用了ThreadLocal
中的childValue
方法,改构造方法的传入参数为父线程的ThreadLocalMap
对象:
private ThreadLocalMap(ThreadLocal.ThreadLocalMap parentMap) {
ThreadLocal.ThreadLocalMap.Entry[] parentTable = parentMap.table;
int len = parentTable.length;
this.setThreshold(len);
this.table = new ThreadLocal.ThreadLocalMap.Entry[len];
ThreadLocal.ThreadLocalMap.Entry[] tab = parentTable;
int tabLen = parentTable.length;
for(int i = 0; i < tabLen; ++i) {
ThreadLocal.ThreadLocalMap.Entry e = tab[i];
if (e != null) {
ThreadLocal<Object> key = (ThreadLocal)e.get();
if (key != null) {
Object value = key.childValue(e.value);
ThreadLocal.ThreadLocalMap.Entry c = new ThreadLocal.ThreadLocalMap.Entry(key, value);
int h;
for(h = key.threadLocalHashCode & len - 1; this.table[h] != null; h = nextIndex(h, len)) {
}
this.table[h] = c;
++this.size;
}
}
}
}
可以看到该ThreadLocalMap
的构造方法根据父线程的ThreadLocalMap
对象创建了新的ThreadLocalMap
对象。而新ThreadLocalMap
对象中key
的值是通过调用ThreadLocal
对象的childValue
方法获取的。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
public InheritableThreadLocal() {
}
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
可以看到InheritableThreadLocal
类只是简单的覆盖了ThreadLocal
类的childValue
方法,getMap
方法和createMap
方法。