Java-并发.02.ThreadLocal

ThreadLocal

java.lang.ThreadLocal<T> 是一个为线程提供线程局部变量的工具类。为线程提供一个线程私有的变量副本,这样多个线程都可以随意更改自己线程局部的变量,不会影响到其他线程。

首次调用threadLocal.get()方法时会调用initialValue()赋一个初始值。

例子: 1.8之前提供的SimpleDateFormat不是线程安全的, 下面的代码用ThreadLocal 解决这个问题:

private final static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};

// or 设置当前线程的ThreadLocal 为新的对象
// threadLocal.set(new SimpleDateFormat("yy-MM-dd"));

// 读取当前线程的ThreadLocal 内保存的对象
DateFormat localFormatter = (DateFormat)threadLocal.get();

注: JDK1.8的 DateTimeFormatter是线程安全的.

实现

我们需要知道 Thread 类中有一个 Map 类型的 threadLocals 成员:

public class Thread {
ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocalMap 该类为一个采用线性探测法实现的 HashMap (区别于 HashMap 的拉链法),
这个 HashMap 的 Entry 继承了 WeakReference<ThreadLocal<?>> (类似 WeakHashMap)

调用 threadLocal.set(Val) 设置新对象时。实际是向 thread.ThreadLocalMap 插入对象,创建新的 Entry(K,V),K 即是 threadLocal 对象,并且从 Entry 的构造函数可以看到, K 是弱引用的,并且没有给弱引用设置一个回收用的 queue,这一点区别 WeakHashMap(Java-Tutorials.13.引用(Reference)),也就是说 threadLocal 并不会像 WeakHashMap 那样自动清理 Key 被回收掉的 Entry

// Entry的构造
Entry(ThreadLocal<?> k, Object v) {
super(k); // 调用了WeakReference(k)
value = v;
}

//
private void set(ThreadLocal<?> key, Object value) {

// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);

// 开放定址法,i不断向后+1,尝试找到每个 entry
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {

// entry继承自 WeakReference,这里调用了 Reference.get(),获取的是Key
ThreadLocal<?> k = e.get();

// case1: key相同(即同一个 threadLocal对象 ),覆盖旧value
if (k == key) {
e.value = value;
return;
}

// case2: 之前 threadLocal的Entry,key已经被GC回收
if (k == null) {
// 将新的entry放入 tab[i],
// 并将旧entry.value=null 帮助回收
replaceStaleEntry(key, value, i); // 内部调用了 expungeStaleEntry()
return;
}
}

// 在tab[i]没有找到过去的 threadLocal的Entry,新创建一个:
tab[i] = new Entry(key, value);
int sz = ++size;
// 清理槽位失败且Entry数组长度超过阈值,重新rehash对Entry数组扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

从 ThreadLocal 中 get 值的时候,首先通过 Thread.currentThread() 得到当前线程,然后拿到这个线程的 ThreadLocalMap,从 Map 取得 Entry,最后从 Entry 取得中的 value 值;

ThreadLocal 的 set、get 、remove 方法最终都会调用 expungeStaleEntry(),找到已经被 GC 清理过的 Entry,将 value 置 null 帮助回收;

ThreadLocalMap 与 WeakHashMap 的比较:

  • 二者的 Entry 都继承自 WeakReference,对 Key 的处理也都是弱引用;
  • ThreadLocalMap 是开放定址,WeakHashMap 使用了链表
  • ThreadLocalMap 没有为 Key 的弱引用指定 Queue,WeakHashMap 指定了 Queue,这也导致二者回收方式不同:
    • WeakHashMap: get() put(), size() 时通过 queue 获取被清理的 entry,设置 entry.value = null 帮助回收,@ref Java-Tutorials.13.引用(Reference)
    • ThreadLocalMap:见 expungeStaleEntry()

@ref: 一文详解JDK中的ThreadLocal(全网最透彻易懂) - 掘金

应用场景

  • ThreadLocal 适用方法调用链上参数的透传,但要注意是同线程间,但不适合异步方法调用的场景。对于异步方法调用,想做参数的透传可以采用阿里开源的 TransmittableThreadLocal。权限、日志、事务等框架都可以利用 ThreadLocal 透传重要参数。

  • 使用 ThreadLocal 保存 Connection:

public class ConnectionManager {

private static final ThreadLocal<Connection> dbConnectionLocal = new ThreadLocal<Connection>() {
@Override
protected Connection initialValue() {
try {
return DriverManager.getConnection("", "", "");

} catch (SQLException e) {
e.printStackTrace();
}
return null;
}

};

public Connection getConnection() {
return dbConnectionLocal.get();
}
}

需要注意,因为 thread.ThreadLocalMap 的 Entry(k,v),其 K 是用 WeakReference 引用的,但是上面的代码中,K 是 static final 的强引用,所以每个线程 ThreadLocalMap 里的 Entry.value 都不会回收;上面的例子中 ThreadLocal 对象是 static 的,所以 key 也不会回收;

InheritableThreadLocal

如果在父线程中创建 ThreadLocal,会发现父线程设置的值在子线程中无法获取,JDK中有InheritableThreadLocal解决此问题。

public class SubThreadUsage {

private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();

public static void main(String[] args) {
threadLocal.set(1);

// 新启一个线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadLocal.get() -> " + threadLocal.get());
}
}).start();
}
}

实现

InheritableThreadLocal 继承了 ThreadLocal,并且数据存放在Thread的 类变量的 inheritableThreadLocals中,变量类型是 ThreadLocal.ThreadLocalMap;
在 Thread 构造方法调用的 init() 中,可看见如果 parent.inheritableThreadLocals不为空,则 ThreadLocal.createInheritedMap()拷贝 ThreadLocalMap,注意这里的拷贝是浅拷贝。子线程如果修改了继承自父线程的ThreadLocal,其他的子线程也可能会看到这个改变。

@ref: alibaba/TransmittableThreadLocal