0
点赞
收藏
分享

微信扫一扫

ThreadLocal 深入源码分析&应用场景介绍


ThreadLocal 深入源码分析&应用场景介绍_数组

ThreadLocal 深入源码分析&应用场景介绍

文章目录

  • ​​ThreadLocal 深入源码分析&应用场景介绍​​
  • ​​1. 示例代码​​
  • ​​2. API​​
  • ​​3. ThreadLocal 原理猜想​​
  • ​​4. 源码分析​​
  • ​​4.1 set()​​
  • ​​4.1.1 createMap​​
  • ​​4.1.2 弱引用设计​​
  • ​​4.1.3 replaceStaleEntry​​
  • ​​4.1.3.1 向前有脏 Entry 向后找到有可覆盖的 Entry​​
  • ​​4.1.3.2 向前有脏 Entry 向后未找到可覆盖的 Entry​​
  • ​​4.1.3.3 向前没有脏 Entry 向后找到可覆盖的 Entry​​

  • ​​4.1.3.4 向前没有脏 Entry 向后未找到可覆盖的 Entry​​
  • ​​4.2 get()​​
  • ​​4.3 remove()​​
  • ​​5. 内存泄露​​
  • ​​6. 子线程上下文同步​​
  • ​​6.1 如何解决跨线程的上下文线程变量传递呢?​​
  • ​​6.2 跨线程同步变量如何从主线程保持同步?​​
  • ​​6.3 父子跨线程如何优雅传递线程变量呢?​​
  • ​​7. 总结​​
  • ​​更多​​

线程级内存变量,不受其他线程的并发影响

在 JDK 1.2 的版本中就提供 java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。

ThreadLocal 并不是一个Thread,而是 Thread 的局部变量,也许把它命名为 ThreadLocalVariable 更容易让人理解一些。

在 JDK 5.0 中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID)

ThreadLocal 是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal 比直接使用 synchronized 同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

应用场景

在 Java 的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用 synchronized 或是 Lock 工具类等方式来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。

最常见的ThreadLocal使用场景为用来解决数据库连接、Session管理、用户信息线程上下文管理等。

1. 示例代码

public class ThreadLocalExample {

private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

public static void main(String[] args) {
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
Integer integer = threadLocal.get();
threadLocal.set(integer += 5);
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
});
}
for (Thread thread : threads) {
thread.start();
}

}
}
// 输出
Thread-2:5
Thread-3:5
Thread-0:5
Thread-1:5
Thread-4:5

2. API

接口

说明

set()

当前线程范围内,设置一个存储到 ThreadLocal 中,这个值仅对当前线程可见。相当于在当前线程范围内创建了副本

get()

从当前线程范围内取出 set 方法设置的值

remove()

移除当前线程中存储的值

withInitial()

JDK8提供,构造器静态方法,创建一个指定初始值的 ThreadLocal 实例

3. ThreadLocal 原理猜想

猜想

  • 能够实现线程的隔离,当前保存的数据,只会存储在当前线程范围内(线程私有的)。
  • 存储结构:当前线程信息作为 KEY,存储的值为 VALUE 的 Map 结构

实际原理分析

ThreadLocal 深入源码分析&应用场景介绍_java_02

1、每个线程使用独立的私有存储结构,ThreadLocalMap

2、每个线程的 ThreadLocalMap 内部的 Entry 的 Key 是弱引用,指向 ThreadLocal 实例的引用

4. 源码分析

4.1 set()

设置线程变量值

public void set(T value) {
Thread t = Thread.currentThread();
// 如果当前线程已经初始化了 map,则设置当前 ThreadLocal 实例引用作为 key,对应的值为 value
// 反之则初始化
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}

// 获取当前线程内的所有 ThreadLocal 的引用
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

// 设置值到 ThreadLocalMap
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); // 计算数组下标1

// 开放寻址,线性探索
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {


ThreadLocal<?> k = e.get();

// 下标 i 的位置已经有值,则直接替换
if (k == key) {
e.value = value;
return;
}

// 下标 i 的位置没有值,则进行 replaceStaleEntry (替换空余的数组)
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}

tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

4.1.1 createMap

初始化 ThreadLocalMap

// 创建线程内部的 ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

// 初始化ThreadLocalMap
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 默认长度为16的数组
table = new Entry[INITIAL_CAPACITY];
// 计算数组下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 把 key,value 存储到数组下标为 i 的位置,弱引用的 KEY
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

4.1.2 弱引用设计

强引用、弱引用差别

public class ReferenceExample {

public static Object object = new Object();

public static Object object2 = new Object();

public static void main(String[] args) {

Object strongRef = object;

// help gc
object = null;

System.out.println("gc 前:" + strongRef);
System.gc();
System.out.println("gc 后:"+strongRef); // 强引用 java.lang.Object@3cda1055

//----- 分割线

WeakReference<Object> weakReference = new WeakReference<>(object2);
// help gc
object2 = null;
System.out.println("gc 前:"+weakReference.get());
System.gc();
System.out.println("gc 后:"+weakReference.get());

}
}

// 输出
gc 前:java.lang.Object@6537cf78
gc 后:java.lang.Object@6537cf78
gc 前:java.lang.Object@67b6d4ae
gc 后:null

从输出可以明显看出,gc 之后,弱引用会被清除,强引用则不会被 gc 回收。

线性探索方式

ThreadLocal 深入源码分析&应用场景介绍_数组_03

  • key=null,表示 ThreadLocal 对象已经被回收
4.1.3 replaceStaleEntry
  • 如果当前值对应的 entry 数组中的 key 为 null,那么该方法会向前查找存在 key 失效的 entry,进行清理
  • 通过线性探索的方式,解决向后查找存在的 hash 冲突问题,替换并覆盖当前位置的 key 失效元素,这样下次再查询的时候可以直接拿到最新的覆盖后的结果

Key 清理过程四种情况

  • 向前有脏 Entry 向后找到有可覆盖的 Entry
  • 向前有脏 Entry 向后未找到可覆盖的 Entry
  • 向前没有脏 Entry 向后找到可覆盖的 Entry
  • 向前没有脏 Entry 向后未找到可覆盖的 Entry

private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;

// Back up to check for prior stale entry in current run.
// We clean out whole runs at a time to avoid continual
// incremental rehashing due to garbage collector freeing
// up refs in bunches (i.e., whenever the collector runs).
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null) // 向前查找到 key 失效的节点
slotToExpunge = i;

// Find either the key or trailing null slot of run, whichever
// occurs first
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();

// If we find key, then we need to swap it
// with the stale entry to maintain hash table order.
// The newly stale slot, or any other stale slot
// encountered above it, can then be sent to expungeStaleEntry
// to remove or rehash all of the other entries in run.
if (k == key) { // 查找 Hash 冲突的节点,进行替换
e.value = value;

tab[i] = tab[staleSlot];
tab[staleSlot] = e;

// Start expunge at preceding stale entry if it exists
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(
// 清理失效的元素
expungeStaleEntry(slotToExpunge), len);
return;
}

// If we didn't find stale entry on backward scan, the
// first stale entry seen while scanning for key is the
// first still present in the run.
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}

// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);

// If there are any other stale entries in run, expunge them
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

4.1.3.1 向前有脏 Entry 向后找到有可覆盖的 Entry

找到 key 为 null 的(key 失效),记录下标到 slotToExpunge

ThreadLocal 深入源码分析&应用场景介绍_开发语言_04

向后查找:key 失效的话进行线性探索,寻找是否存在 Hash 冲突的节点(key 相同), 交换初始位置,避免 0x29 的 key 冲突重复的问题。

ThreadLocal 深入源码分析&应用场景介绍_开发语言_05

清理逻辑:

通过前后的边界定位( 两边元素为 null),遍历数组,找到 key 为 null,value 非 null 的元素,把 value 设置为 null。

4.1.3.2 向前有脏 Entry 向后未找到可覆盖的 Entry

探索式扩大搜索范围

ThreadLocal 深入源码分析&应用场景介绍_开发语言_06

边界范围内,没有 hash 冲突,则无需覆盖和替换,直接插入即可。

4.1.3.3 向前没有脏 Entry 向后找到可覆盖的 Entry

向后查到有 key 冲突的节点,替换并覆盖当前位置的数据

ThreadLocal 深入源码分析&应用场景介绍_java_07

ThreadLocal 深入源码分析&应用场景介绍_java_08

4.1.3.4 向前没有脏 Entry 向后未找到可覆盖的 Entry

ThreadLocal 深入源码分析&应用场景介绍_开发语言_09

4.2 get()

获取线程变量

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();
}

private T setInitialValue() {
T value = initialValue(); // 实际调用子类 SuppliedThreadLocal 的 initialValue 方法,获取初始值
Thread t = Thread.currentThread();
// 获取当前线程中的 ThreadLocalMap 集合
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}

// java.lang.ThreadLocal#initialValue
protected T initialValue() {
return null;
}


public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}

static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

private final Supplier<? extends T> supplier;

SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}

@Override
protected T initialValue() {
return supplier.get();
}
}

  • 从当前线程缓存 ThreadLocalMap ,根据 ThreadLocal 缓存引用查找存在当前线程中的值

4.3 remove()

传入当前 ThreadLocal 的引用,清除在当前线程中的存储的值。清除的过程中会遍历数组,删除失效的 key 元素

public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}

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;
}
}
}

5. 内存泄露

get/set 方法本身会调用 replaceStaleEntry 清除失效的 key,但是极端情况下,会发生未清除的情况(key=null,value 非空,元素存储空间未释放)。

注意:使用的时候没有 remove 的话会存在内存泄露问题。所以 set 方法一般和 remove 成对出现。

6. 子线程上下文同步

根据前文介绍,ThreadLocal 是 Thread 级别内部存储了 ThreadLocal 的弱引用来进行变量的线程隔离。如果当前线程中又开启了一个子线程,在子线程想要拿到父线程的中的 ThreadLocal 值怎么办呢?比如会有以下的这种代码的实现。由于 ThreadLocal 的实现机制,在子线程中 get 时,我们拿到的 Thread 对象是当前子线程对象,那么他的 ThreadLocalMap 是 null 的,所以我们得到的 value 也是null。

private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

public static void main(String[] args) {

threadValueCanNotTransferWithThreadLocal();
}

private static void threadValueCanNotTransferWithThreadLocal() {
// 开启一个线程
new Thread(() -> {
threadLocal.set(threadLocal.get() + 1);
System.out.println("1--0" + Thread.currentThread().getName() + ":" + threadLocal.get());
// 开启子线程
new Thread(() -> {
System.out.println("1--1" + Thread.currentThread().getName() + ":" + threadLocal.get());
}).start();
}).start();
}
// 输出
1--0Thread-0:1
1--1Thread-1:0

1–1 子线程中获取不到父线程 1–0 中设置的变量值。

6.1 如何解决跨线程的上下文线程变量传递呢?

使用 InheritableThreadLocal 可以帮助我们解决。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {


protected T childValue(T parentValue) {
return parentValue;
}

/**
* 重写Threadlocal类中的getMap方法,在原 Threadlocal 中是返回
* t.theadLocals,而在这么却是返回了 inheritableThreadLocals,因为
* Thread 类中也有一个要保存父子传递的变量
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}

/**
* 同理,在创建ThreadLocalMap的时候不是给t.threadlocal赋值
* 而是给inheritableThreadLocals变量赋值
*
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}

ThreadLocal

/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}

private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];

for (Entry e : parentTable) {
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++;
}
}
}
}

Thread 初始化时存在 ThreadLocal.createInheritedMap 的调用,当我们 new Thread 的时候,会 copy 父线程 parent 的 map,创建一个新的 map 赋值给当前线程的 inheritableThreadLocals。

private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {

// 其他初始化代码忽略...

//关注此处:copy父线程parent的 map,创建一个新的map赋值给当前线程的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 */
this.tid = nextThreadID();
}

父子线程演示

private static InheritableThreadLocal<Integer> threadLocal2 = new InheritableThreadLocal<Integer>();

public static void main(String[] args) {

threadValueTransferWithInheritableThreadLocal();
}

private static void threadValueTransferWithInheritableThreadLocal() {
// 再开启一个线程
new Thread(() -> {
threadLocal2.set(1);
System.out.println("2--0" + Thread.currentThread().getName() + ":" + threadLocal2.get());
// 开启子线程
new Thread(() -> {
System.out.println("2--1" + Thread.currentThread().getName() + ":" + threadLocal2.get());
}).start();
}).start();
}

// 输出
2--0Thread-0:1
2--1Thread-1:1

从打印的结果可以看出:2–1 对应的子线程成功获得了主线程 2–0 的父线程变量值。

6.2 跨线程同步变量如何从主线程保持同步?

但是从代码中我们可以看到,子线程使用的变量是从主线程复制过来的,如果父线程这个时候更新了变量值,子线程还能拿到么?前文代码分析中可以看出这里是引用复制,但是简单值是无法传递的。我们先看看简单值的拷贝能否和父线程中值的更新保持同步?

private static InheritableThreadLocal<Integer> threadLocal3 = new InheritableThreadLocal<Integer>();

public static void main(String[] args) {

threadTransferSimpleObject();
}

private static void threadTransferSimpleObject() {
// 再开启一个线程
new Thread(() -> {
threadLocal3.set(1);
System.out.println("2--0--1" + Thread.currentThread().getName() + ":" + threadLocal3.get());
// 开启子线程
new Thread(() -> {
System.out.println("2--1--1" + Thread.currentThread().getName() + ":" + threadLocal3.get());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2--1--2" + Thread.currentThread().getName() + ":" + threadLocal3.get());
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadLocal3.set(2);
System.out.println("2--0--2" + Thread.currentThread().getName() + ":" + threadLocal3.get());
}).start();
}

// 输出
2--0--1Thread-0:1
2--1--1Thread-1:1
2--0--2Thread-0:2
2--1--2Thread-1:1

简单变量父线程 2–0–2 先更新为2,但是子线程 2–1–2 中 获取到的还是1,之前拷贝过来的值。

简单值无法传递,如果是引用传递呢?在子线程可以拿到么?

private static InheritableThreadLocal<IntegerHolder> threadLocal4 = new InheritableThreadLocal<IntegerHolder>();

static class IntegerHolder {

@Setter
private Integer value;

public IntegerHolder(Integer value) {
this.value = value;
}

@Override
public String toString() {
return String.valueOf(value);
}
}

public static void main(String[] args) {

threadTransferMultipleObject();
}

private static void threadTransferMultipleObject() {
// 再开启一个线程
new Thread(() -> {
threadLocal4.set(new IntegerHolder(1));
System.out.println("3--0--1" + Thread.currentThread().getName() + ":" + threadLocal4.get());
// 开启子线程
new Thread(() -> {
System.out.println("3--1--1" + Thread.currentThread().getName() + ":" + threadLocal4.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3--1--1" + Thread.currentThread().getName() + ":" + threadLocal4.get());
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadLocal4.get().setValue(2);
System.out.println("3--0--2" + Thread.currentThread().getName() + ":" + threadLocal4.get());
}).start();
}

// 输出
3--0--1Thread-0:1
3--1--1Thread-1:1
3--0--2Thread-0:2
3--1--1Thread-1:2

3–0–2 作为父线程,更新了值,由于是引用拷贝,3–1–1 子线程中也成功更新了。

6.3 父子跨线程如何优雅传递线程变量呢?

transmittable-thread-local 阿里巴巴的一个开源工具类包。在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal 值的传递功能,解决异步执行时上下文传递的问题。

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.12.6</version>
</dependency>

使用方式

  • 官网使用说明:https://github.com/alibaba/transmittable-thread-local

和 InheritableThreadLocal 有什么差异?

JDK 的 InheritableThreadLocal 类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的 ThreadLocal 值传递已经没有意义,应用需要的实际上是把任务提交给线程池时的 ThreadLocal 值传递到任务执行时。

适用场景

ThreadLocal 的需求场景即 TransmittableThreadLocal 的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的执行组件情况下传递ThreadLocal值』则是 TransmittableThreadLocal 目标场景。

下面是几个典型场景例子。

  • 分布式跟踪系统 或 全链路压测(即链路打标)
  • 日志收集记录系统上下文
  • Session 级 Cache
  • 应用容器或上层框架跨应用代码给下层SDK传递信息

使用 JDK 的 InheritableThreadLocal 线程池场景下的问题示例

private static ThreadLocal<String> threadLocal6 = new InheritableThreadLocal<String>();

private static ThreadPoolExecutor executorService = new ThreadPoolExecutor(2, 2,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());

private static void transmittableThreadLocalNotRefresh() {
threadLocal6.set("1");
System.out.println("MAIN-1" + Thread.currentThread().getName() + ":" + threadLocal6.get());

Runnable runnable1 = () -> {
System.out.println("5--0--1" + Thread.currentThread().getName() + ":" + threadLocal6.get());

};
Callable<String> callable1 = () -> {
System.out.println("5--0--2" + Thread.currentThread().getName() + ":" + threadLocal6.get());
return "success";
};
executorService.submit(runnable1);
executorService.submit(callable1);
// 注意此处已经使用了线程池中所有的线程

threadLocal6.set("2");
System.out.println("MAIN-2" + Thread.currentThread().getName() + ":" + threadLocal6.get());

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

Runnable runnable2 = () -> {
System.out.println("5--1--1" + Thread.currentThread().getName() + ":" + threadLocal6.get());
};
Callable<String> callable2 = () -> {
System.out.println("5--1--2" + Thread.currentThread().getName() + ":" + threadLocal6.get());
return "success";
};
System.out.println("THREAD-POOL-INFO,poolSize:" + executorService.getPoolSize());
// 此时复用线程池中的2个线程,2个 Worker 从线程的等待队列中获取任务执行,由于没有新建线程,因此此时主线程的线程变量是无法更新到线程池的任务线程中的
executorService.submit(runnable2);
executorService.submit(callable2);

executorService.shutdown();

}

// 输出
MAIN-1main:1
MAIN-2main:2
5--0--1pool-1-thread-1:1
5--0--2pool-1-thread-2:1
THREAD-POOL-INFO,poolSize:2
5--1--1pool-1-thread-1:1
5--1--2pool-1-thread-2:1

截图中可以看出任务队列中任务执行完后,当前线程池的执行线程还是在的,只是处于从队列中获取任务的阻塞状态。

ThreadLocal 深入源码分析&应用场景介绍_java_10

使用 TransmittableThreadLocal 来解决上述问题

// 注意此处,需要使用 TTH -> TransmittableThreadLocal
private static final ThreadLocal<String> threadLocal7 = TransmittableThreadLocal.withInitial(() -> "0");

private static ThreadPoolExecutor executorService = new ThreadPoolExecutor(2, 2,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());

private static void transmittableThreadLocalAutoRefresh() {
threadLocal7.set("1");
System.out.println("MAIN-1" + Thread.currentThread().getName() + ":" + threadLocal7.get());

Runnable runnable1 = () -> {
System.out.println("5--0--1" + Thread.currentThread().getName() + ":" + threadLocal7.get());

};
Callable<String> callable1 = () -> {
System.out.println("5--0--2" + Thread.currentThread().getName() + ":" + threadLocal7.get());
return "success";
};
executorService.submit(TtlRunnable.get(runnable1));
executorService.submit(TtlCallable.get(callable1));
// 注意此处已经使用了线程池中所有的线程

threadLocal7.set("2");
System.out.println("MAIN-2" + Thread.currentThread().getName() + ":" + threadLocal7.get());

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

Runnable runnable2 = () -> {
System.out.println("5--1--1" + Thread.currentThread().getName() + ":" + threadLocal7.get());
};
Callable<String> callable2 = () -> {
System.out.println("5--1--2" + Thread.currentThread().getName() + ":" + threadLocal7.get());
return "success";
};
System.out.println("THREAD-POOL-INFO,poolSize:" + executorService.getPoolSize());
// 此时复用线程池中的2个线程,2个 Worker 从线程的等待队列中获取任务执行,虽然没有新建线程,
// 但是使用 TtlRunnable 、TtlCallable 包装了线程任务,因此此时主线程的线程变量 可以 更新到线程池的任务线程中的.
executorService.submit(TtlRunnable.get(runnable2));
executorService.submit(TtlCallable.get(callable2));

executorService.shutdown();
}

//输出

MAIN-1main:1
5--0--1pool-1-thread-1:1
MAIN-2main:2
5--0--2pool-1-thread-2:1
THREAD-POOL-INFO,poolSize:2
5--1--1pool-1-thread-1:2
5--1--2pool-1-thread-2:2

7. 总结

  • ThreadLocal 可以进行对象的跨线程传递,线程之间变量互相隔离,从当前的线程局部内存中直接获取。ThreadLocalMap 中管理了 ThreadLocal 对象引用。当调用 ThreadLocal 实例变量的 get 方法时,会从当前线程的 ThreadLocalMap 局部内存中,通过 ThreadLocal 实例变量的引用获取对应线程局部内存中存储的值。
  • 线程内部局部变量 ThreadLocalMap 的 Key 存储的是 ThreadLocal 的引用,为弱引用,当 ThreadLocal 数据失效的时候,弱引用会被自动回收,当清理策略扫描到时,会覆盖当前 KEY 为 null 的数组元素。
  • 常用场景:
  • 日志采集,获取线程上下文 trace 等信息
  • 线程间数据隔离
  • 数据库连接等 Session 存储、连接池管理
  • 过滤器等获取用户请求上下文信息,跨层传递
  • ThreadLocal 针对的线程级别的局部变量副本管理,无法支持主子线程变量同步,如有这方面诉求可以使用 InheritableThreadLocal
  • 线程池中的线程是循环复用的,这种 InheritableThreadLocal 基于父子线程关系的 ThreadLocal 值传递已经没有意义,可以使用阿里开源的 TransmittableThreadLocal 来解决线程池中线程复用时,无法从主线程获取线程变量的问题。

更多

ThreadLocal 深入源码分析&应用场景介绍_数组_11


举报

相关推荐

0 条评论