ThreadLocal的API很少就包含了4个,分别是get()、set()、remove()、withInitial(),源码如下:
public T get() {}
public void set(T value){}
public void remove(){}
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
}
get():从当前线程的 ThreadLocalMap 获取与当前 ThreadLocal 对象对应的值。如果 ThreadLocalMap 中不存在该值,则调用 setInitialValue() 方法进行初始化。
set(T value):将当前线程的 ThreadLocalMap 中的值设置为给定的 value。如果当前线程没有 ThreadLocalMap,则会创建一个新的 ThreadLocalMap 并将值设置进去。
remove():从当前线程的 ThreadLocalMap 中移除与当前 ThreadLocal 对象对应的值,帮助防止内存泄漏。
withInitial(Supplier<? extends T> supplier):返回一个新的 ThreadLocal 对象,其初始值由 Supplier 提供。这允许使用者在创建 ThreadLocal 时指定初始值。
针对这几个源码我们重点进行分析和体会。
ThreadLocal.set()方法源码详解
pubic void set(T value) {
// 获取当前线程
Thread t = Threac.currentThread();
// 获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 如果map不为null, 调用ThreadLocalMap.set()方法设置值
if (map != null)
map.set(this, value);
else
// map为null,调用createMap()方法初始化创建map
createMap(t, value);
}
// 返回线程的ThreadLocalMap.threadLocals
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 调用ThreadLocalMap构造方法创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// ThreadLocalMap构造方法,传入firstKey, firstValue
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化Entry表的容量 = 16
table = new Entry[INITIAL_CAPACITY];
// 获取ThreadLocal的hashCode值与运算得到数组下标
int i = firsetKey.threadLocalHashCode & (INITAL_CAPACITY - 1);
// 通过下标Entry表赋值
table[i] = new Entry(firstKey, firstValue);
// Entry表存储元素数量初始化为1
size = 1;
// 设置Entry表扩容阙值 默认为 len * 2 / 3
setThreshold(INITIAL_CAPACITY);
}
private void setThreshold(int len) {
threshold = len * 2 / 3
}
ThreadLocal.set()方法还是很简单的,核心方法在ThreadLocalMap.set()方法
基本流程可总结如下:
ThreadLocalMap.get()方法详解
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 未找到的话,则调用setInitialValue()方法设置null
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// key相等直接返回
if (e != null && e.get() == key)
return e;
else
// key不相等调用getEntryAfterMiss()方法
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 迭代往后查找key相等的entry
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
// 遇到key=null的entry,先进行探测式清理工作
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
主要包含两种情况,一种是hash计算出下标,该下标对应的Entry.key和我们传入的key相等的情况,另外一种就是不相等的情况。
相等情况:相等情况处理很简单,直接返回value,如下图,比如get(ThreadLocal1)计算下标为4,且4存在Entry,且key相等,则直接返回value = 11:
不相等情况:不相等情况,以get(ThreadLocal2)为例计算下标为4,且4存在Entry,但key相等,这个时候则为往后迭代寻找key相等的元素,如果寻找过程中发现了有key = null的元素则回进行探测式清理操作。如下图:
迭代到index=5的数据时,此时Entry.key=null,触发一次探测式数据回收操作,执行expungeStaleEntry()方法,执行完后,index 5、8的数据都会被回收,而index 6、7的数据都会前移,此时继续往后迭代,到index = 6的时候即找到了key值相等的Entry数据,如下图:
ThreadLocal.remove()方法源码详解
public void remove() {
// 获取当前线程的 ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
// 如果当前线程有 ThreadLocalMap,则在 map 中移除当前 ThreadLocal 的值
m.remove(this);
}
static class ThreadLocalMap {
// 内部 Entry 类,继承自 WeakReference<ThreadLocal<?>>
static class Entry extends WeakReference<ThreadLocal<?>> {
// ThreadLocal 对应的值
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 线程局部变量哈希表
private Entry[] table;
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
// 计算当前 ThreadLocal 的哈希值在数组中的索引位置
int i = key.threadLocalHashCode & (len - 1);
// 从hash获取的下标开始,寻找key相等的entry元素清除
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear(); // 清除键的引用
expungeStaleEntry(i); // 清除相应的值
return;
}
}
}
// 用于计算下一个索引位置
private int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
// 清除无效的 Entry
private void expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 清除给定槽位的 Entry
tab[staleSlot].value = null;
tab[staleSlot] = null;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
}
}
ThreadLocal.remove()核心是调用ThreadLocalMap.remove()方法,流程如下:
通过hash计算下标。
从散列表该下标开始往后查key相等的元素,如果找到则做清除操作,引用置为null,GC的时候key就会置为null,然后执行探测式清理处理。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/xiaofeng10330111/article/details/139667074