ThreadLocal用于线程之间的变量共享,每一个线程都有自己独立的副本,用来存储自己的变量。只要线程是活跃的,其对应的副本就是可以访问的。当线程死亡,其副本会作为垃圾被回收。
ThreadLocal.java中的相关代码:
public class ThreadLocal<T> {
…
}
1.泛型T为存储和获取数据的类型。
ThreadLocal.java中的相关代码:
// ThreadLoacal的哈希值,用来区分不同线程的ThreadLocal
private final int threadLocalHashCode = nextHashCode();
// 用于计算哈希值,采用AtomicInteger保证并发时安全,初始值为0
private static AtomicInteger nextHashCode = new AtomicInteger();
// 用于计算哈希值
private static final int HASH_INCREMENT = 0x61c88647;
用于产生ThreadLocal的哈希值。
ThreadLocal.java中的相关代码:
private static int nextHashCode() {
// 将nextHashCode的值加上HASH_INCREMENT的值,并返回
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
ThreadLocal.java中的相关代码:
public ThreadLocal() {
}
用于为ThreadLocal提供当前线程的初始化值。
默认为空,如果有需要,可以重写该方法。
ThreadLocal.java中的相关代码:
protected T initialValue() {
return null;
}
获取指定线程的ThreadLocalMap。
ThreadLocal.java中的相关代码:
ThreadLocalMap getMap(Thread t) {
// 返回线程的全局变量
return t.threadLocals;
}
为指定线程创建ThreadLocalMap用于保存线程间共享的对象,并存储firstValue。
ThreadLocal.java中的相关代码:
void createMap(Thread t, T firstValue) {
// 创建ThreadLocalMap对象
// 保存到线程的全局变量threadLocals中
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
该方法用于根据父线程的ThreadLocalMap对象创建子线程继承的ThreadLocalMap对象。
ThreadLocal.java中的相关代码:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
// 调用构造方法,创建新对象
return new ThreadLocalMap(parentMap);
}
该方法在InheritableThreadLocal类中被重写,用于根据父值而给出子值。
该方法为了在必要时继承的子类可以不用重写ThreadLocalMap类,而通过此方法实现原本的功能。
ThreadLocal.java中的相关代码:
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
删除当前线程在ThreadLocal中存储的值。
ThreadLocal.java中的相关代码:
public void remove() {
// 获取当前线程的ThreadLocalMap对象
ThreadLocalMap m = getMap(Thread.currentThread());
// 若ThreadLocalMap不为空
if (m != null)
m.remove(this); // 移除当前线程存储的值
}
保存值到当前线程的ThreadLocalMap中。
ThreadLocal.java中的相关代码:
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 若ThreadLocalMap对象存在
if (map != null)
// 直接保存value
map.set(this, value);
else // 若ThreadLocalMap对象不存在
// 则创建ThreadLocalMap对象,并保存value
createMap(t, value);
}
用于将当前线程的ThreadLocalMap存储的值设置为初始值。
ThreadLocal.java中的相关代码:
private T setInitialValue() {
// 获取初始值
T value = initialValue();
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 若ThreadLocalMap对象存在
if (map != null)
map.set(this, value); // 则保存值value
else // 若ThreadLocalMap对象不存在
createMap(t, value); // 则创建ThreadLocalMap对象,再保存值value
// 返回初始值
return value;
}
获取当前线程的ThreadLocalMap中对应的值。
ThreadLocal.java中的相关代码:
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 若ThreadLocalMap对象存在
if (map != null) {
// 尝试获取当前ThreadLocal对应的键值对
ThreadLocalMap.Entry e = map.getEntry(this);
// 若键值对存在,说明之前存储过
if (e != null) {
@SuppressWarnings("unchecked")
// 获取键值对的值
T result = (T)e.value;
// 返回
return result;
}
}
// 若ThreadLocalMap对象不存在,
// 则将ThreadLocal对应的值设置为初始值,并返回
return setInitialValue();
}
ThreadLocalMap是ThreadLocal用于存储键值对的内部类,ThreadLocal中对数据操作都是通过该内部类提供的方法实现的。
ThreadLocal.java中的相关代码:
static class ThreadLocalMap {
…
}
ThreadLocal.java中的相关代码:
// 初始化容量,表示能够存储键值对的数量,必须是2的次方形式
private static final int INITIAL_CAPACITY = 16;
// 用于存储键值对的数组
private Entry[] table;
// 用于记录已经存储的键值对的数量
private int size = 0;
// 扩容的阈值,存储的键值对数量超过该值将会触发扩容,默认为0
private int threshold;
该类用于存储键值对,其中键的类型为ThreadLocal,值的类型为Object。
同时,该类继承了弱引用类WeakReference,当获取键为空时,说明该键的引用被回收。
ThreadLocal.java中的相关代码:
static class Entry extends WeakReference<ThreadLocal<?>> {
// 线程中存储的值
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
该方法会创建ThreadLocalMap,并将参数中的键值对进行保存。
ThreadLocal.java中的相关代码:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 根据初始化容量,创建键值对数组
table = new Entry[INITIAL_CAPACITY];
// 获取ThreadLocal的哈希值,和INITIAL_CAPACITY – 1进行与运算
// 计算键值对在数组中的位置
// INITIAL_CAPACITY – 1作为掩码
// 如16-1=15,1111,最后进行与运算得到的数值一定在0到15之间
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 创建新的键值对,保存到数组对应的位置中
table[i] = new Entry(firstKey, firstValue);
// 记录当前键值对数量1
size = 1;
// 设置扩容阈值
setThreshold(INITIAL_CAPACITY);
}
该方法用来根据继承的ThreadLocalMap的内容创建新的ThreadLocalMap。
该方法是私有的,仅供createInheritedMap方法调用。
ThreadLocal.java中的相关代码:
private ThreadLocalMap(ThreadLocalMap parentMap) {
// 获取继承的ThreadLocalMap中的键值对数组
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];
// 若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);
// 根据键ThreadLocal的哈希值
// 计算出键值对在数组中的位置h
// len - 1作为掩码
// 如16-1=15,1111,
// 最后进行与运算得到的数值一定在0到15之间
int h = key.threadLocalHashCode & (len - 1);
// 循环,若数组的h位置有键值对
// 则根据当前位置h和数组长度,获取下一个位置
// 直到找到一个没有键值对的位置
while (table[h] != null)
h = nextIndex(h, len);
// 将键值对存在对应位置
table[h] = c;
// 当前存储的键值对数量加1
size++;
}
}
}
}
用来设置扩容阈值。
ThreadLocal.java中的相关代码:
private void setThreshold(int len) {
// 阈值为数组长度的三分之二
threshold = len * 2 / 3;
}
获取位置i的下一个位置。
ThreadLocal.java中的相关代码:
private static int nextIndex(int i, int len) {
// 若i的下一个位置i+1小于数组长度len
// 则返回i+1,否则返回0
// 该操作相当于i对len取模
return ((i + 1 < len) ? i + 1 : 0);
}
获取位置i的上一个位置。
ThreadLocal.java中的相关代码:
private static int prevIndex(int i, int len) {
// 若i的上一个位置i-1大于0
// 则返回i-1,否则返回len-1
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
删除指定位置的键值对,并对其它位置的键值对重新散列整理。
ThreadLocal.java中的相关代码:
private int expungeStaleEntry(int staleSlot) {
// 获取数组
Entry[] tab = table;
// 获取数组的长度
int len = tab.length;
// 删除指定位置的键值对
tab[staleSlot].value = null;
tab[staleSlot] = null;
// 存储的键值对数量减1
size--;
// 对数组循环整理,重新散列,
// 直到遇到为空的位置,停止循环
Entry e;
int i;
// 从staleSlot下一个位置i开始
// 若i位置的键值对e为空,则跳出循环
// 每次循环i向后移动一个位置,若移动到最后一个位置,则回到0开始
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
// 从i位置的键值对e中获取键k
ThreadLocal<?> k = e.get();
// 若键为空
if (k == null) {
// 清除当前位置的键值对
e.value = null;
tab[i] = null;
// 存储的键值对数量减1
size--;
} else { // 若键不为空
// 重新计算键值对应该存放的位置h
int h = k.threadLocalHashCode & (len - 1);
// 若键值对应该存储的位置和当前存放的位置不一致
if (h != i) {
// 对当前的位置进行清空
tab[i] = null;
// 循环,从键值对应该存放的位置h开始找
// 找到一个空位置,存放键值对
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
// 返回跳出循环时键值对为空的位置
return i;
}
从位置i的下一位开始,向下查看log2(n)个位置,如果发现位置不对的键值对,则对其进行清理。
ThreadLocal.java中的相关代码:
private boolean cleanSomeSlots(int i, int n) {
// 标志位,如果进行过清理,则为true
boolean removed = false;
// 获取键值对数组
Entry[] tab = table;
// 获取数组长度
int len = tab.length;
// 循环
do {
// 获取位置i的下一个位置,结果再赋给i
i = nextIndex(i, len);
// 获取该位置的键值对
Entry e = tab[i];
// 若该位置的键值对存在,并且键值对的键不存在
if (e != null && e.get() == null) {
// 重新设置循环次数
n = len;
// 设置标志位
removed = true;
// 删除指定位置的键值对,并进行整理
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0); // 循环log2(n)次
// 返回结果
return removed;
}
删除数组中键不存在的键值对,对位置错误的键值对进行调整。
ThreadLocal.java中的相关代码:
private void expungeStaleEntries() {
// 获取键值对数组
Entry[] tab = table;
// 获取数组长度
int len = tab.length;
// 循环遍历键值对数组
for (int j = 0; j < len; j++) {
// 获取键值对
Entry e = tab[j];
// 若键值对存在,并且键值对的键不存在
if (e != null && e.get() == null)
// 删除该位置的键值对,并整理。
expungeStaleEntry(j);
}
}
用于对键值对数组扩容,扩大之前容量的2倍。
ThreadLocal.java中的相关代码:
private void resize() {
// 获取旧的键值对数组
Entry[] oldTab = table;
// 获取旧的键值对数组的长度
int oldLen = oldTab.length;
// 根据旧长度计算新长度,新长度为旧长度的2倍
int newLen = oldLen * 2;
// 根据新长度创建新数组
Entry[] newTab = new Entry[newLen];
// 用于记录新数组使用的空间数量
int count = 0;
// 循环遍历旧数组
for (int j = 0; j < oldLen; ++j) {
// 取出键值对
Entry e = oldTab[j];
// 若键值对存在
if (e != null) {
// 获取键值对的键
ThreadLocal<?> k = e.get();
// 若键不存在
if (k == null) {
// 则将其值删除,即释放引用
e.value = null; // 可以帮助垃圾回收器快点回收
} else { // 若键存在
// 重新计算其在新数组中的位置
int h = k.threadLocalHashCode & (newLen - 1);
// 若该位置已经被占了,则循环查找下一个可以存放的位置
while (newTab[h] != null)
h = nextIndex(h, newLen);
// 存放键值对到新数组中
newTab[h] = e;
// 新数组已经使用的空间数量加1
count++;
}
}
}
// 设置新的扩容阈值
setThreshold(newLen);
// 将局部变量保存到全局变量
// 将已经使用的空间数量保存到全局变量
size = count;
// 将新数组保存到全局变量
table = newTab;
}
对所有的键值对进行整理,删除键不存在的键值对。
如果整理后键值对的数量还是过高,则进行扩容。
ThreadLocal.java中的相关代码:
private void rehash() {
// 整理所有键值对。
expungeStaleEntries();
// 当数组已经使用的空间数量超过阈值的四分之三
// 即总空间的六分之一,则进行扩容
// 使用更低的阈值避免滞后现象
if (size >= threshold - threshold / 4)
resize();
}
该方法用于在set方法中将staleSlot位置处的键值对替换成键为key值为value的键值对。
ThreadLocal.java中的相关代码:
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
// 获取键值对数组
Entry[] tab = table;
// 获取键值对数组的长度
int len = tab.length;
Entry e;
// slotToExpunge用来标记需要进行删除的位置
int slotToExpunge = staleSlot;
// 循环向前查找,找到一个键值对存在,
// 但键值对中键不存在(被回收)的位置,直到遇到空的键值对结束循环
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i; // 用slotToExpunge标记该位置
// 循环向后查找键值对,直到遇到空的键值对结束循环
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 若键相等
// 即本来要插入在staleSlot位置,但现在在i位置找到了对应的键
if (k == key) {
// 修改键的值为新值
e.value = value;
// 交换i位置和staleSlot位置的键值对
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 若二者相等,说明之前循环向前查找没有找到要找的键值对
if (slotToExpunge == staleSlot)
// 标记需要删除的位置i,
// i位置的键值对已经更新并转移到staleSlot位置
slotToExpunge = i;
// 删除i位置的键值对,并对键值对数组进行整理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
// 返回
return;
}
// 若k为空同时slotToExpunge和staleSlot相等
// k为空说明,当前的i位置是满足键值对存在但键不存在的位置
// slotToExpunge和staleSlot相等说明该位置之前向前查找时没有找到
if (k == null && slotToExpunge == staleSlot)
// 标记需要删除的位置i
slotToExpunge = i;
}
// 代码执行到此,说明循环中没有找到存在的键值对,
// 该键值对之前没有存储过
// 删除staleSlot位置的值(释放引用,加速垃圾回收)
tab[staleSlot].value = null;
// 创建新键值对并存储
tab[staleSlot] = new Entry(key, value);
// 若slotToExpunge和staleSlot不相等
// 说明在循环向后查找中找到了需要删除的键值对
if (slotToExpunge != staleSlot)
// 删除slotToExpunge位置的键值对,并进行整理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
移除键为key的键值对。
ThreadLocal.java中的相关代码:
private void remove(ThreadLocal<?> key) {
// 获取键值对数组
Entry[] tab = table;
// 获取键值对数组长度
int len = tab.length;
// 计算键值对应该存储的位置
int i = key.threadLocalHashCode & (len-1);
// 从i位置循环向后查找,直到遇到空键值对结束循环
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 若i位置的键值对的将和要移除的键值对的键相等
if (e.get() == key) {
// 释放键值对的引用(加速垃圾回收)
e.clear();
// 删除位置i的键值对e
expungeStaleEntry(i);
// 返回
return;
}
}
}
添加键值对。
ThreadLocal.java中的相关代码:
private void set(ThreadLocal<?> key, Object value) {
// 获取键值对数组
Entry[] tab = table;
// 获取键值对数组长度
int len = tab.length;
// 计算键值对应该存储的位置i
int i = key.threadLocalHashCode & (len-1);
// 循环从位置i向后查找,直到遇到空键值对跳出循环
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 获取键值对的键
ThreadLocal<?> k = e.get();
// 若i位置键值对的键k和要添加的键值对的键key相等
// 说明之前添加过
if (k == key) {
// 更新键值对的值为新的值value
e.value = value;
// 返回
return;
}
// 若当前位置i的键值对的键不存在(被回收)
if (k == null) {
// 将新的键值对添加到位置i处
replaceStaleEntry(key, value, i);
// 返回
return;
}
}
// 代码执行到此处,说明该键值对之间没有添加过
// 并且也没有合适的位置
// 创建新的键值对,保存到位置i
tab[i] = new Entry(key, value);
// 当前存储的键值对数量加1,并将其值赋给sz
int sz = ++size;
// 若整理空间失败,同时当前存储的键值对数量超过了扩容阈值
// 说明当前存储空间不足
if (!cleanSomeSlots(i, sz) && sz >= threshold)
// 重新散列,删除键不存在的键值对,必要时将进行扩容
rehash();
}
1.ThreadLocal是用于在线程中存储数据,其内部是通过ThreadLocalMap来存储的,每一个线程都有一个对应ThreadLocalMap,ThreadLocalMap的内部维护了一个Entry数组。
2.一个Entry就是一个键值对,该键值对键的类型为ThreadLocal,值的类型为Object。其中,由于Entry继承了WeakReference,因此每个键值对Entry对键ThreadLocal的引用都是弱引用。
3.每个ThreadLocal对象被创建时,都有不同的哈希值,该值会通过计算得到以该ThreadLocal为键的键值对的存储位置。当我们调用ThreadLocal的get和set方法,会将以该ThreadLocal为键的键值对存储到当前线程的ThreadLocalMap中。
4.ThreadLocalMap存储在线程中,但是对ThreadLocalMap的操作(如:创建、整理、删除、添加、获取…)须要通过ThreadLocal对象的方法来实现。
5.ThreadLocalMap内部提供方法,会对键被回收的键值对进行清理,以保证空间存储需要的键值对,当清理后的空间还是不足时,会触发扩容。ThreadLocalMap的初始容量为16,每次扩容会会扩大之前的2倍。