简介
多线程访问同一个共享变量时特别容易出现并发问题,为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步,同步的措施一般是加锁,但加锁会影响性能。
那么有没有一种方式可以做到,当创建变量后每个线程对其进行访问的时候,访问的是自己线程的变量呢? ThreadLocal 就可以做这件事。
创建 ThreadLocal 变量后,每个线程都会复制该变量到自己的本地内存,当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。

使用
public class ThreadLocalTest {
// 创建 ThreadLocal 变量
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
// 打印函数
static void print(String name) {
System.out.println(name + ":" + threadLocal.get());
// 清除当前线程中本地变量 threadLocal 的值
threadLocal.remove();
}
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
// 设置线程 t1 本地变量 threadLocal 的值
threadLocal.set("8:45");
print("t1");
// 清除后打印本地变量 threadLocal 的值
System.out.println("after remove t1:"+threadLocal.get());
});
Thread t2 = new Thread(() -> {
// 设置线程 t2 本地变量 threadLocal 的值
threadLocal.set("2020-5-28");
print("t2");
// 清除后打印本地变量 threadLocal 的值
System.out.println("after remove t2:"+threadLocal.get());
});
t1.start();
t2.start();
}
}

当线程 t1 、 t2 访问 threadLocal 变量时,访问的都是线程的本地内存中的值,不会发生资源冲突,也就没有线程安全问题。
源码分析
Thread 类中有两个变量,都是 ThreadLocalMap 类型的变量,每个线程的 ThreadLocal 型变量都存在 threadLocals 里面。很显然,Map 结构表示每个线程能关联多个 ThreadLocal 类型的变量。
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
我们来看看 ThreadLocal 类的工作原理。
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的 threadLocals 变量
ThreadLocalMap map = getMap(t);
// 如果 threadLocals 变量不空,则直接设值
if (map != null)
map.set(this, value);
else
// 如果 threadLocals 变量未空,则初始化
createMap(t, value);
}
// 获取线程 t 的 threadLocals
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 初始化 线程 t 的 threadLocals
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap 是一个 HashMap 结构,key 是当前 ThreadLocal 的实例对象引用,value 则是通过 set 方法设的值,Hhaspmap 的具体源码就不分析了。
public T get() {
Thread t = Thread.currentThread();
// 获取当前线程的 threadLocals 变量
ThreadLocalMap map = getMap(t);
if (map != null) {
// 以当前 ThreadLocal 的实例对象引用作为 key
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 返回 key 对应的 value
T result = (T)e.value;
return result;
}
}
// 如果当前线程 threadLocals 变量未空,则先初始化
return setInitialValue();
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
// 以当前 ThreadLocal 的实例对象引用作为 key
m.remove(this);
}
总结:
每个线程都有 threadLocals 成员变量,该变量类型为 HashMap,其中 key 为我们定义的 ThreadLocal 变量的 this 引用, value 为我们使用 set 方法设的值。每个线程的 ThreadLocal 型变量都存放在线程自己的 threadLocals 变量中。
ThreadLocal 没有继承性
public class ThreadLocalTest {
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("顺溜");
Thread t1 = new Thread(()->{
System.out.println("t1:" + threadLocal.get());
});
t1.start();
System.out.println("main:"+threadLocal.get());
}
}

同一个 ThreadLocal 型变量在父线程中被设置值后,在子线程中是获取不到的。这是正常现象,因为父线程 ( main ) 和 子线程 ( t1 ) 是两个不同的线程。那么有没有办法让子线程能访问到父线程中设置的值? 答案是有。
InheritableThreadLocal
InheritableThreadLocal 继承自 ThreadLocal ,其提供了一个特性,是让子线程可以访问在父线程中设置的本地变量。
使用
public class ThreadLocalTest {
static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("顺溜");
Thread t1 = new Thread(()->{
System.out.println("t1:" + threadLocal.get());
});
t1.start();
System.out.println("main:"+threadLocal.get());
}
}

源码分析
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
InheritableThreadLocal 重写了三个方法,在 InheritableThreadLocal 的世界里,变量 inheritableThreadLocals 取代了 threadLocals。
InheritableThreadLocal 是如何让子线程可以访问父线程的本地变量呢?这得从 Thread 类的构造说起。
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
// 获取当前线程(父线程)
Thread parent = currentThread();
...
// inheritThreadLocals默认为true, 如果父线程的 inheritableThreadLocals 不为 null
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
// 设置子线程中的 inheritableThreadLocals 交量
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
// 从给定的 parentMap 中构造出一个新的 Map,只在 createInheritedMap 方法中调用。
// 十分类似 HashMap 的带参构造(思路一模一样),这里就不分析源码了。
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++;
}
}
}
}
总结
InheritableThreadLocal 类通过重写 getMap 和 createMap 方法,让本地变量保存到了具体线程的 inheritableThreadLocals 变量里面。当父线程创建子线程时,构造函数会把父线程中的 inheritableThreadLocals 变量复制一份保存到子线程的 inheritableThreadLocals 变量中。
那么在什么情况下需要子线程可以获取父线程的 threadlocals 变量呢?情况还是蛮多的,比如子线程需要使用存放在父线程 threadlocals 中的用户登录信息,再比如一些中间件,需要把统一的 id 追踪 的整个调用链路记录下来。这些情况下InheritableThreadLocal 就显得很有用