创建一个 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的 valueThreadLocalMap.Entry e = map.getEntry(this);// 如果可以找到,则直接返回该 valueif (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);elsecreateMap(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// 如果是第一次调,创建线程对应的的 HashMapcreateMap(t, value);}public void remove() {// 获取当前线程的ThreadLocalMapThreadLocalMap m = getMap(Thread.currentThread());// map不为空,则移除当前ThreadLocal作为key的键值对。if (m != null)m.remove(this);}ThreadLocalMap getMap(Thread t) {// 获取线程自己的 threadLocalsreturn 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);}@Overrideprotected 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的大小为16table = new Entry[INITIAL_CAPACITY];// 通过hashcode & (长度-1)的位运算,确定键值对的位置int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);// 创建一个新节点保存在table当中table[i] = new Entry(firstKey, firstValue);// 存放元素数量为 1size = 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 停止,返回 nullwhile (e != null) {// 获取节点对应的 keyThreadLocal<?> 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) {// 新开一个引用指向tableEntry[] 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) {// 新开一个引用指向tableEntry[] 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 existsif (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 slottab[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) {// 新的引用指向tableEntry[] tab = table;// 获取长度int len = tab.length;// 先将传过来的下标位置 设置为nulltab[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当中的keyThreadLocal<?> k = e.get();// 如果ThreadLocal为null,则将value以及数组下标所在位置设置null,方便GCif (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 hysteresisif (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 方法有多种,比如创建线程时传入父线程中的变量,将其复制到子线程。但这都改变了使用习惯。
