o从名字我们就可以看到ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
首先从使用 ThreadLocal 开始看,源码如下
ThreadLocal<Integer> thi = new ThreadLocal<>();
可以看出来 ThreadLocal 仅仅是 new 出来,再来看看构造函数, 可以看出来构造函数什么都没做,由于 ThreadLocal 本身不继承任何类也没实现接口所以,new 一个 ThreadLocal 的时候什么都没做,只是初始化一个对象。
public ThreadLocal() {
}
那么 ThreadLocal 什么时候初始化存储空间呢。这就要看 set 方法。
主要流程:
- 先获取 ThreadLocalMap 对象,
- 如果没初始化则初始化
- 如果已经初始化则设置值
public void set(T value) {
Thread t = Thread.currentThread();
// 获取 map 对象
ThreadLocalMap map = getMap(t);
if (map != null)
// 设置值
map.set(this, value);
else
// 初始化
createMap(t, value);
}
getMap
用于获取线程的 threadLocals 成员变量。
ThreadLocalMap getMap(Thread t) {
// 获取线程的 threadLocals 成员变量
return t.threadLocals;
}
线程定义 threadLocals 成员变量代码。线程本身没有初始化该成员变量的方法。ThreadLocalMap 是 ThreadLocal 一个内部静态类,其本身提供类似 Map 的存储结构。
ThreadLocal.ThreadLocalMap threadLocals = null;
所以初始是在 createMap
方法中,可以看出只是 new 一个 ThreadLocalMap 对象,并将当前 ThreadLocal 对象和值当做参数传递过去。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
继续跟进,看看 ThreadLocalMap 的构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// new 一个数组,默认大小是 16
table = new Entry[INITIAL_CAPACITY];
// 获取 ThreadLocal 的 threadLocalHashCode 对容量取模获取索引位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 对 ThreadLocal 对象和 value 包装成 Entry 然后放入数组。
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
这里需要注意 Entry 是弱引用对象
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
关于 ThreadLocal 成员变量生成方式 threadLocalHashCode,先来看成员变量定义,由代码可以知道每次都是调用 nextHashCode 获取
private final int threadLocalHashCode = nextHashCode();
nextHashCode
方法由代码可以知道是由 nextHashCode 生成每次加上成员变量HASH_INCREMENT
获得。所以所谓 hashCode 就是一个整数每次原子操作加上一个固定的值。
private static AtomicInteger nextHashCode = new AtomicInteger();
// 1640531527
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
再来看看 ThreadLocalMap 的 set 方法。
主流程
- 计算槽位
- 根据槽位获取对象,如果是统一 ThreadLocal 则修改值,如果 ThreadLocal 为 null 则清理槽位,并设置值。否则就是槽位冲突获取下一个位置知道找到位置
- 找到合适位置将值添加进去
- 清理操作
- 如果条件符合则扩容
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 先获取所以位置 Entry ,如果存在则进入 for 循环,每次操作获取当前操作的下一个操作
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
// 获取 Entry 对象中的 ThreadLocal
ThreadLocal<?> k = e.get();
// 如果是同一个对象,修改值就直接返回
if (k == key) {
e.value = value;
return;
}
// 如果 ThreadLocal 对象已经回收则清理
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 找到合适位置,将当前 ThreadLocal 和值包装成 Entry 添加到数组
tab[i] = new Entry(key, value);
int sz = ++size;
// 然后清理一些槽位
if (!cleanSomeSlots(i, sz) && sz >= threshold)
// 重新 hash,扩容
rehash();
}
先看第一个获取槽位 nextIndex
,由代码可以知道获取槽位是一个 ringbuffer 的方式,如果 i 是最后一个位置则返回第一个位置,即 0;
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
再看看 replaceStaleEntry
主要流程
- 向前找看看是否有需要清除的槽位
- 向找看看是否有需要清除的槽位,在此过程中如果有同一个 ThreadLocal 则修改值,并清理指定槽位
- 将当前 ThreadLocal 对象和 value 赋值到对应槽位
- 如果有需要清理槽位,清除掉
/**
* @param key ThreadLocal 对象
* @param value 需要设置的值
* @param staleSlot 槽位
*/
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
// 获取槽位前一个操作,如果槽位非 null 进入 for 循环
for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)){
// 如果前一个槽位 ThreadLocal 对象是 null,则更新 slotToExpunge 重新循环直到,找到一个没有 Entry 的槽位
if (e.get() == null)
slotToExpunge = i;
}
// 获取staleSlot 下一个槽位如果 Entry 非 null 则进入 for 循环
for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
// 获取当前槽位 ThreadLocal 对象
ThreadLocal<?> k = e.get();
// 如果 ThreadLocal 一致 则修改 value
if (k == key) {
e.value = value;
// 修改值
tab[i] = tab[staleSlot];
// 将 staleSlot 槽位赋值
tab[staleSlot] = e;
// 如果清除槽位和入参一致,则修改清除槽位值
if (slotToExpunge == staleSlot)
slotToExpunge = i;
// 清理一些槽位
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// 如果 ThreadLocal 是 null, 且清楚槽位还没修改,则修改。
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 清除槽位的 value
tab[staleSlot].value = null;
// 重新赋值槽位 Entry 对象
tab[staleSlot] = new Entry(key, value);
// 如果运行中还有其他任何陈旧条目,则将它们清除
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
prevIndex
方法,获取槽位采用 ringbuffer 方式从后向前获取,如果当前槽位是第一个槽位则取最后槽位。
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
expungeStaleEntry
清除指定槽位 Entry 对象
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 清除 value 和 Entry 对象
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 重新哈希直到遇到null
Entry e;
int i;
for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 前一个槽位 ThredLocal 是 null 则清除掉
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// 非 null
int h = k.threadLocalHashCode & (len - 1);
// 重新哈希后槽位变了
if (h != i) {
// 清除原先槽位
tab[i] = null;
// 向后获取槽位直到为 null
while (tab[h] != null)
h = nextIndex(h, len);
// 重新设定值
tab[h] = e;
}
}
}
return i;
}
cleanSomeSlots
清除一些槽位
/**
* @param i 槽位
* @param n 槽位总数
*
* @return 如果有有被清除的槽位返回 true
*/
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];
// ThreadLocal 是 null 则清除
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
现在看看 ThreadLocal 的 get 方法,其主流程如下
- 获取 ThreadLocalMap 对象
- 获取值
- 没有初始化则初始化,并返回 null
public T get() {
Thread t = Thread.currentThread();
// 获取 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
// 获取值
if (map != null) {
// 获取 Entry 对象
ThreadLocalMap.Entry e = map.getEntry(this);
// Entry 对象非 null 则获取其中 value
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 没有初始化则初始化,并返回 null
return setInitialValue();
}
setInitialValue
负责初始化,这种情况就是直接设置 null 值。
private T setInitialValue() {
// 获取 null 值
T value = initialValue();
Thread t = Thread.currentThread();
// 获取 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
// 已经创建则设置 null
if (map != null)
map.set(this, value);
else
// 创建并设置对象
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
ThreadLocalMap 的 getEntry 方法
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 如果取模的槽位就是当前 ThreadLocal 对象则直接返回
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
取模对应槽位的 Entry 对应 ThreadLocal 不符合则通过 getEntryAfterMiss 获取,主流程如下
- Entry 对象的 ThreadLocal 对象如果一直则返回
- 如果 ThreadLocal 是空则清除当前槽位
- 否则获取下一个槽位
- 找不到则返回 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];
}
// 找不到返回 null
return null;
}
ThreadLocal 如果避免内存泄漏必须 remove,由代码可以知道调用的是 ThreadLocalMap 的 remove 方法。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap 的 remove 方法
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 取模槽位对应 Entry 对象非 null 才删除
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
// ThreadLocal 对象一致才删除
if (e.get() == key) {
// 清除弱引用指向的 ThreadLocal 对象
e.clear();
// 清除槽位
expungeStaleEntry(i);
return;
}
}
}