创建一个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。
多线程操作该变量时,实际操作的是自己的副本。
Thread 类中有 threadLocals 和 inheritableThreadLocals,都是 ThreadLocalMap 类型的变量。
默认情况下,每个线程中这两个变量都为 null,当线程第一次调用 ThreadLocal 的 set 或 get 方法才会创建它们。
每个线程的本地变量不是存放在 ThreadLocal 实例里面,而是存放在调用线程的 threadLocals 变量里面。
也就是说,ThreadLocal 类型的本地变量存放在具体的线程内存空间中。
Thread 里面的 threadLocals 为何为 map 结构?
因为每个线程可以关联多个 ThreadLocal 变量
ThreadLocal
package java.lang;
import java.lang.ref.*;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class ThreadLocal<T> {
/**ThreadLocal 通过常量 threadLocalHashCode 来标识每一个 ThreadLocal 的唯一性
*/
private final int threadLocalHashCode = nextHashCode();
/**
* 下一个 hash code 通过 AtomicInteger 的CAS 操作 保证原子性 初始值为0
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* Hash增量 ,它可以使hashcode均匀的分布在大小为2的N次方的数组里
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**(初始值为0)的基础上每次累加0x61c88647*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
protected T initialValue() {
return null;
}
/**
* 支持Lambda表达式,和ThreadLocal重写的initialValue()效果一样。
* ThreadLocal<Integer> seqNum = ThreadLocal.withInitial(() -> 0);
* @since 1.8
*/
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
public ThreadLocal() {
}
/**
* 获取当前线程存的值,如果没有则拷贝
*/
public T get() {
Thread t = Thread.currentThread();
// 获取当前线程的 threadLocals 变量
ThreadLocalMap map = getMap(t);
if (map != null) {
// 如果线程 threadLocals 有值,则获取 以当前实例对象为key的 value
ThreadLocalMap.Entry e = map.getEntry(this);
// 如果可以找到,则直接返回该 value
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 找不到就初始化,并返回初始化的值
return setInitialValue();
}
/**
* setInitialValue()调用重写的initialValue()返回新值(如果没有重写initialValue将返回 * 默认值null),
* 并将新值存入当前线程的ThreadLocalMap(如果当前线程没有ThreadLocalMap,会先创建一个)
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 将当前线程作为 key,去查找对应的线程变量,找到则设置值
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 如果是第一次调,创建线程对应的的 HashMap
createMap(t, value);
}
public void remove() {
// 获取当前线程的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
// map不为空,则移除当前ThreadLocal作为key的键值对。
if (m != null)
m.remove(this);
}
ThreadLocalMap getMap(Thread t) {
// 获取线程自己的 threadLocals
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
// 创建线程自己的 threadLocals
// threadLocals 的类型就是下面的 ThreadLocalMap
// key 就是当前 ThreadLocal 的实例对象引用,value 就是 set 的值
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* 在子线程创建的时候会去拷一份父线程的inheritableThreadLocals
*/
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
/**
* 方法childValue在子类InheritableThreadLocal中定义
*/
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
/**
* withInitial 返回的
*/
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
/**
* 线程放 threadLocal 对象的值的容器
*/
static class ThreadLocalMap {
/**
* 之所以用弱引用,是为了解决线程与ThreadLocal之间的强绑定关系
* 会导致如果线程没有被回收,则GC便一直无法回收这部分内容
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* 初始容量,必须 2 的 n 次方,为了减少碰撞,能够让保存的元素尽量的分散
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 数组容器,容量为 2 的 n 次方
*/
private Entry[] table;
/**
* 当前存放的 ThreadLocal 元素数量
*/
private int size = 0;
/**
* 下一次扩容的阀值
*/
private int threshold; // Default to 0
/**
* 设置下一次阀值为当前的 二分之三
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 获取下一个索引,超出长度则返回0,所以 entry数组实际上是一个环形结构
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* 返回上一个索引,如果-1为负数,返回长度-1的索引
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* 构造函数,ThreadLocal为key,泛型为value
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// // 初始化table的大小为16
table = new Entry[INITIAL_CAPACITY];
// 通过hashcode & (长度-1)的位运算,确定键值对的位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 创建一个新节点保存在table当中
table[i] = new Entry(firstKey, firstValue);
// 存放元素数量为 1
size = 1;
// 更新下次扩容阀值
setThreshold(INITIAL_CAPACITY);
}
/**
* 构造函数,传入 父线程的 threadLocals 对象
* 这是InheritableThreadLocal提供了了一种父子间数据共享的机制
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
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++;
}
}
}
}
/**
* 获取指定 ThreadLocal 的索引位置,通过下标索引获取内容
*/
private Entry getEntry(ThreadLocal<?> key) {
// 计算下标
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
// 线性探测法, 找不到的话接着从i位置开始向后遍历
return getEntryAfterMiss(key, i, e);
}
/**
* 线性探测
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 循环向后遍历,直到找到了其他不相等的 key 停止,返回 null
while (e != null) {
// 获取节点对应的 key
ThreadLocal<?> k = e.get();
// 相等就是找到了,返回
if (k == key)
return e;
// 如果为null,触发一次连续段清理
if (k == null)
expungeStaleEntry(i);
else
// 获取下一个下标接着进行判断
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
/**通过这个方法,我们可以看出该哈希表是用线性探测法来解决冲突的*/
private void set(ThreadLocal<?> key, Object value) {
// 新开一个引用指向table
Entry[] tab = table;
// 获取table的长度
int len = tab.length;
// 获取对应ThreadLocal在table当中的下标
int i = key.threadLocalHashCode & (len-1);
/**
* 从该下标开始循环遍历
* 1、如遇相同key,则直接替换value
* 2、如果该key已经被回收失效,则替换该失效的key
*/
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
// 如果 k 为 null,则替换当前失效的k所在Entry节点
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 找到空的位置,创建Entry对象并插入
tab[i] = new Entry(key, value);
int sz = ++size;
// 如果到达扩容阀值,执行扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* Remove the entry for key.
*/
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) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
// 新开一个引用指向table
Entry[] tab = table;
int len = tab.length;
Entry e;
// 记录当前失效的节点下标
int slotToExpunge = staleSlot;
/**
* 通过这个for循环的prevIndex(staleSlot, len)可以看出
* 这是由staleSlot下标开始向前扫描
* 查找并记录最前位置value为null的下标
*/
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
/**
* 通过for循环nextIndex(staleSlot, len)可以看出
* 这是由staleSlot下标开始向后扫描
*/
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
// 获取Entry节点对应的ThreadLocal对象
ThreadLocal<?> k = e.get();
/**
* 如果与新的key对应,直接赋值value
* 则直接替换i与staleSlot两个下标
*/
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// Start expunge at preceding stale entry if it exists
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// i之前的节点里,没有value为null的情况
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
/**
* 使用的是弱引用,那便有可能在GC的时候就被回收了。所以需要清理
* 如果有很多Entry节点已经被回收了,但是在table数组中还留着位置,不清理就会浪费资源
* 在清理节点的同时,可以将后续非空的Entry节点重新计算下标进行排放,get的时候就能快速定位资源,加快效率。
*/
private int expungeStaleEntry(int staleSlot) {
// 新的引用指向table
Entry[] tab = table;
// 获取长度
int len = tab.length;
// 先将传过来的下标位置 设置为null
tab[staleSlot].value = null;
tab[staleSlot] = null;
// 更新当前元素个数 --
size--;
// 遍历删除指定节点所有后续节点当中,ThreadLocal被回收的节点
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
// 获取entry当中的key
ThreadLocal<?> k = e.get();
// 如果ThreadLocal为null,则将value以及数组下标所在位置设置null,方便GC
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// 如果后续节点不为空 重新计算key的下标
int h = k.threadLocalHashCode & (len - 1);
// 如果位置不变,则下一个
// 如果位置变了,当前位置设为 null,根据线性探测把 e 设置到新位置
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
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];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
/**
*重新包装和/或调整桌子的尺寸。首先扫描整个系统
*表删除过时的条目。如果这还不够
*缩小表的大小,将表的大小增加一倍。
*/
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
/**
* 把容量增加一倍。
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
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; // 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;
}
/**
* 删除表中所有过时的条目。
*/
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);
}
}
}
}
InheritableThreadLocal
�让子线程可以访问在父线程中设置的本地变量。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* 在子线程复制父线程值时调用,子类可以重写该方法,处理子线程复制的初始值
*/
protected T childValue(T parentValue) {
return parentValue;
}
/**
* get也变成了 获取 inheritableThreadLocals
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* 第一次 set 时创建的是 inheritableThreadLocals,而不是 threadLocals 了
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
什么情况下需要子线程获取父线程的 threadLocal 变量呢?
比如子线程需要使用存放在 threadLocal 变量中的用户登录信息,
比如中间件需要把同一的 id 追踪的整个调用链路记录下来。
子线程使用父线程的 threadLocal 方法有多种,比如创建线程时传入父线程中的变量,将其复制到子线程。但这都改变了使用习惯。