目录
一、ThreadLocal介绍
二、ThreadLocal核心方法解析
1) initialValue()
2) setIntialValue()
3) set()
4) get()
ThreadLocal是怎么做到多线程环境下副本隔离的?
三、ThreadLocalMap 源码解析
1) ThreadLocalMap是在什么时候初始化的呢?
2) Thread和value在ThreadLocalMap里是怎么存放的?
3) InheritablThreadLocal又是怎么存放的?
四、InheritableThreadLocal核心原理
1) inheritableThreadLocal究竟是何物?
2) 案例演示
ThreadLocal 能够保证在多线程情况下,每个线程独有一份对象副本,线程之间的副本互相独立、隔离。
在每个线程类Thread实例里拥有二份ThreadLocal.ThreadLocalMap, 一份为当前线程的threadLocals,另一份为当前线程的inheritableThreadLocals, 他们都是静态内部类,因此这2个thredLocalMap在堆里都只有一份。inheritableThreadLocals能够将当前主线程设置的变量值传给子线程的inheritableThreadLocals。
用来初始化threadLocal的默认值, 默认为null。
protected T initialValue() {
return null;
}
当set()方法被覆盖时,此方法替代set()方法的用法
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 获取当前线程的threadLocalMap, 如果为空那么createMap, 如果不为空,那么更新map的value值。
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
给当前线程在ThreadLocalMap 设置一个value值,类型为自定义的T
public void set(T value) {
// 1. 获取当前线程
Thread t = Thread.currentThread();
// 2. 获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 3. 查看map是否被初始化,如果被初始化了,那么覆盖掉之前的value, 否则初始化ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
获取当前线程的value值。
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;
}
}
return setInitialValue();
}
是通过ThreadLocalMap实现的。
ThreadLocalMap是ThreadLocal类里的静态内部类, 搞懂ThreadLocalMap就能拨开迷雾见光明。
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
}
跟着问题去探索ThreadLocalMap。
在调用set()方法时时候,然后会执行createMap。
createMap时,会将线程和value都交给ThreadLocalMap。
每个线程存放的值是放在一个名为Entry静态内部类的value属性里,因此value为object类, 一个线程对应一个Entry, 在threadLocalMap类里有一个table, 该table就是用来存放所有线程(包含主线程和所有的子线程)的threadLocal的k和v, k为线程对象,v为object类型的value。
如果是inheritableThreadLocal, 那么创建的map也会添加到ThreadLocalMap静态内部类的Entry[] table里,这样就能保证所有的线程(包含主线程和所有子线程) 都能够独有一份变量副本。
经过一番调试后, 我发现所有的线程和副本最终都会放到ThreadLocalMap的Entry[] 里,这样就解释了每个线程之间的变量是独立的。
inheritableThreadLocal可将主线程设置的变量传到到子线程里。当不懂原理的时候,最好的方式就是debug源代码。
子线程在初始化的时候,会进入到Thread类里的init()方法,然后会查看主线程的inheritableThreadLocal是否为空,如果不为空,那么就将主线程的inheritableThreadLocals复制一份给自己的inheritableThreadLocals。
init()方法代码:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
...
// 将主线程的inheritableThreadLocals复制给子线程的inheritableThreadLocals
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
子线程的inheritableThreadLocals和主线程的一样,因此子线程也能拿到主线程设置的变量。
private static InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>();
@Test
void testThreadLocal() {
inheritableThreadLocal.set(100);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getId() + "_" + inheritableThreadLocal.get());
}).start();
}
}
打印结果: