本文的目的是分析 ThreadLocal 的源码,关于 ThreadLocal 如何使用,请阅读参考资料1。
每个线程对象都有一个 ThreadLocalMap 类型的变量。ThreadLocalMap 是 ThreadLocal 的内部类,是用基于线性探测法的散列表实现的。每一个线程对象可以往 Map 中添加多个 ThreadLocal 对象为键的键值对,每个键对应的值唯一。所以通过一个 ThreadLocal 对象设置的值,在每个线程中都是唯一且互相独立的。唯一是因为键的唯一性,独立是因为每个线程都有自己的ThreadLocalMap内部变量,它是归线程所有的。
// java.lang.ThreadLocal.ThreadLocalMap
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
有趣的是它的元素是弱引用,被弱引用关联着的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被引用关联的对象。因此如果某个线程已经运行结束,不会因为这里有对它的引用而就无法回收,这个键会在回收后变成null。
所以在 ThreadLocalMap
的 set
方法中会调用 rehash
方法,rehash
方法先清理这些已经变成null的弱引用,对后面的部分Entry重新进行哈希散列,最后再判断是否扩容。(推荐阅读参考资料3)
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
public ThreadLocal() {
}
构造器为空。
为当前线程的 ThreadLocalMap 对象添加键值对,键为 ThreadLocal 对象,值为设置的 value 对象。
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 已经调用过set或get方法时,往线程的map中添加新的值,键为当前ThreadLocal对象
if (map != null)
map.set(this, value);
// 没有调用过set或get方法时,为当前线程创建一个ThreadLocalMap
// 并添加第一个值,键为当前ThreadLocal对象
else
createMap(t, value);
}
// 返回Thread对象t的ThreadLocalMap属性,初始为null
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
线程中调用 get 方法时,如果之前用 set 方法为这个 ThreadLocal 键添加过值,那么就返回设置过的值,否则就返回默认初始值 null。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 已经调用过set或get方法时,对象值已经设置过,就返回上一次的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 没有调用过set或get方法时,对象值没有设置过,就自动设置初始值,并返回初始值
return setInitialValue();
}
private T setInitialValue() {
// 获得初始值
T value = initialValue();
// set方法
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
通常可以在新建 ThreadLocal 对象时,采用匿名内部类的方式重写该方法,来设置共享的初始值。详细见参考资料1。
// 这是一个可重写的方法,用来设置ThreadLocal对象在所有线程中的初始值
protected T initialValue() {
return null;
}
使用 InheritableThreadLocal
可以在子线程中获取到父线程在new该线程之前,保存在 InheritableThreadLocal
变量中的对象值。
public class Main {
private static InheritableThreadLocal local =
new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
local.set(1001);
new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
":" + local.get());
}).start();
Thread.sleep(1000);
local.set(2002);
System.out.println(Thread.currentThread().getName() +
":" + local.get());
}
}
/* 输出为:
main:2002
Thread-0:1001
*/
这是因为在每个线程在构造的时候,会复制父线程对象的 inheritableThreadLocals 属性。父线程的 inheritableThreadLocals 对象不为空时,就会复制一份到子线程,复制的是对象引用(不是克隆),之后父线程对 inheritableThreadLocal 变量 set 新的值不再影响子线程,但如果父线程修改值对象在堆中的字段的话,子线程中的值也会变化。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
// inheritThreadLocals = true
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
// 父线程对象
Thread parent = currentThread();
...
// 父线程的 inheritableThreadLocals 对象不为空时,就会复制一份到子线程。
// 复制的是对象引用,之后父线程对 inheritableThreadLocal 变量 set 新的
// 值不再影响子线程,但如果父线程修改对象在堆中的值的话,子线程中的值也会
// 变化
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}