线程间数据传递之ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
线程间数据传递之ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
1、ThreadLocal介绍
spring 中基于 ThreadLocal 来实现事务。
多线程 访问同一个共享变量的时候容易出现并发问题,ThreadLocal是除了加锁这种同步方式之外的一种保证
规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是
线程自己的变量这样就不会存在线程不安全问题。在实际多线程操作的时候,操作的是自己本地内存中的变量,从
而规避了线程安全问题。
ThreadLocal又叫做线程本地变量是为每一个Thread创建的一个变量的副本,每个线程都可以在内部访问到这个副
本。通过这种方式我们在一个线程的生命周期以内安全的访问这个变量,不用担心被其他线程所污染。这是它相对
于全局变量所带来的优势。
package com.example.threadlocalandother;
/**
* @author tom
*/
public class ThreadLocalDemo {
private ThreadLocal<Long> threadLocal = new ThreadLocal<>();
private void fun1() {
threadLocal.set(System.nanoTime());
System.out.println("fun1:" + threadLocal.get());
fun2();
}
private void fun2() {
System.out.println("fun2:" + threadLocal.get());
fun3();
}
private void fun3() {
System.out.println("fun3:" + threadLocal.get());
threadLocal.remove();
}
public static void main(String[] args) {
ThreadLocalDemo demo = new ThreadLocalDemo();
demo.fun1();
}
}
# 程序输出
fun1:1511680153700
fun2:1511680153700
fun3:1511680153700
我们在先创建了一个本地变量threadLocal 在fun1中set值,在其余的方法中我们并没有通过方法传递的方式显示
的将值传递给其他方法,仅仅是通过threadLocal变量get的方式就可以获取到我们所需要的变量,从而实现了变量
的跨方法的传递。可能你觉得这样写没什么好处,我不用threadLocal直接用个全局变量照样可以实现数据的传
递,我们可以改造一下fun1方法。
package com.example.threadlocalandother;
/**
* @author tom
*/
public class ThreadLocalDemo1 {
private ThreadLocal<Long> threadLocal = new ThreadLocal<>();
private void fun1() throws InterruptedException {
threadLocal.set(System.nanoTime());
System.out.println("fun1:" + threadLocal.get());
final Thread t1 = new Thread(() -> {
System.out.println("t1:" + threadLocal.get());
}, "t1");
t1.start();
t1.join();
fun2();
}
private void fun2() {
System.out.println("fun2:" + threadLocal.get());
fun3();
}
private void fun3() {
System.out.println("fun3:" + threadLocal.get());
threadLocal.remove();
}
public static void main(String[] args) throws InterruptedException {
ThreadLocalDemo1 threadLocalDemo1 = new ThreadLocalDemo1();
threadLocalDemo1.fun1();
}
}
# 程序输出
fun1:3554613378000
t1:null
fun2:3554613378000
fun3:3554613378000
按照常理来想那么我们t1线程内也应该能获取到数据,但是结果大相径庭。实际上 threadLocal 对象只是作为了一
个 key,而真正存储数据的是每个线程自身的 thread 内持有的一个 ThreadLocalMap 的对象,而我们的 t1 线程
自然就不能获取到数据。如果使用后不 remove 可能会有内存泄漏的风险!
package com.example.threadlocalandother;
/**
* @author tom
*/
public class ThreadLocalDemo2 {
private ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public void run() {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set(System.nanoTime());
Long aLong = threadLocal.get();
threadLocal.remove();
System.out.println(aLong);
}
}).start();
}
}
public static void main(String[] args) {
ThreadLocalDemo2 threadLocalDemo2 = new ThreadLocalDemo2();
threadLocalDemo2.run();
}
}
# 程序输出
3042907777600
3042907791300
3042908175100
3042908275900
3042908395900
3042908245300
3042908563900
3042908182800
3042908589200
3042908716700
package com.example.threadlocalandother;
/**
* @author tom
*/
public class ThreadLocalDemo3 {
/**
* 创建ThreadLocal变量
*/
private static final ThreadLocal<Integer> LOCAL_VARIABLE = new ThreadLocal<>();
static void print(String str) {
// 打印当前线程本地内存中localVariable变量的值
System.out.println(str + ": " + LOCAL_VARIABLE.get());
// 清除当前线程本地内存中的localVariable变量
LOCAL_VARIABLE.remove();
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 设置线程1中本地变量的值
LOCAL_VARIABLE.set(100);
print("Thread 1");
System.out.println("After remove: " + LOCAL_VARIABLE.get());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// 设置线程2中本地变量的值
LOCAL_VARIABLE.set(200);
print("Thread 2");
System.out.println("After remove: " + LOCAL_VARIABLE.get());
}
});
t1.start();
t2.start();
}
}
# 程序输出
Thread 1: 100
Thread 2: 200
After remove: null
After remove: null
package com.example.threadlocalandother;
/**
* @author tom
*/
public class UserContext implements AutoCloseable {
private final ThreadLocal<String> ctx = new ThreadLocal<>();
public UserContext(String user) {
ctx.set(user);
}
public String currentUser() {
return ctx.get();
}
@Override
public void close() {
ctx.remove();
}
public static void main(String[] args) {
try (UserContext userContext = new UserContext("Bob")) {
// 可任意调用userContext.currentUser():
String currentUser = userContext.currentUser();
System.out.println(currentUser);
} // 在此自动调用UserContext.close()方法释放ThreadLocal关联对象
catch (Exception e) {
e.printStackTrace();
}
}
}
# 程序输出
Bob
使用ThreadLocal的最终目的还是为了得到安全的数据。
问题:同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的,好在InheritableThreadLocal
可以解决这个问题。
2、InheritableThreadLocal介绍
以我们在最开始的demo中t1线程获取不到数据。但是如果我们有这种诉求,希望父线程能够向子线程传递数据
呢,那我们便可以用到InheritableThreadLocal。
如何解决子线程获取父线程的数据就要使用InheritableThreadLocal。
package com.example.threadlocalandother;
/**
* @author tom
*/
public class InheritableThreadLocalDemo {
private ThreadLocal<Long> inheritableThreadLocal = new InheritableThreadLocal<>();
private void fun1() throws InterruptedException {
inheritableThreadLocal.set(System.nanoTime());
System.out.println("fun1:" + inheritableThreadLocal.get());
final Thread t1 = new Thread(() -> {
System.out.println("t1:" + inheritableThreadLocal.get());
}, "t1");
t1.start();
t1.join();
fun2();
}
private void fun2() {
System.out.println("fun2:" + inheritableThreadLocal.get());
fun3();
}
private void fun3() {
System.out.println("fun3:" + inheritableThreadLocal.get());
inheritableThreadLocal.remove();
}
public static void main(String[] args) throws InterruptedException {
InheritableThreadLocalDemo inheritableThreadLocalDemo = new InheritableThreadLocalDemo();
inheritableThreadLocalDemo.fun1();
}
}
# 程序输出
fun1:7459897559400
t1:7459897559400
fun2:7459897559400
fun3:7459897559400
我们可以看到在t1线程中能够正确的获取到结果。
package com.example.threadlocalandother;
/**
* @author tom
*/
public class InheritableThreadLocalDemo1 {
private ThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<>();
public void run() {
//给父类inheritableThreadLocals赋值
inheritableThreadLocal.set(1);
new Thread(new Runnable() {
@Override
public void run() {
//子线程同时去获取自己的inheritableThreadLocals
Integer integer = inheritableThreadLocal.get();
System.out.println(integer);
}
}).start();
Integer integer = inheritableThreadLocal.get();
System.out.println(integer);
inheritableThreadLocal.remove();
}
public static void main(String[] args) {
InheritableThreadLocalDemo1 inheritableThreadLocalDemo1 = new InheritableThreadLocalDemo1();
inheritableThreadLocalDemo1.run();
}
}
# 程序输出
1
1
InheritableThreadLocal类**「继承」**了ThreadLocal类,并重写了childValue、getMap、createMap方法。
总之,InheritableThreadLocals类通过重写getMap和createMap两个方法将本地变量保存到了具体线程的
inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,
就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数
会将父线程的inheritableThreadLocals中的变量**「复制一份到子线程的inheritableThreadLocals」**变量中。
package com.example.threadlocalandother;
import com.alibaba.fastjson.JSONObject;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author tom
*/
public class UserInheritableThreadLocal {
static InheritableThreadLocal<Map<String, String>> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void set(String key, String value) {
Map<String, String> headers = new HashMap<>();
headers.put(key, value);
inheritableThreadLocal.set(headers);
}
public static Map<String, String> get() {
return inheritableThreadLocal.get();
}
public static void remove() {
if (inheritableThreadLocal != null) {
inheritableThreadLocal.remove();
}
}
public static void main(String[] args) {
//设置值
UserInheritableThreadLocal.set("name", "tom");
Map<String, String> stringStringMap = UserInheritableThreadLocal.get();
System.out.println(JSONObject.toJSONString(stringStringMap));
// 创建线程池
ExecutorService executorService = new ThreadPoolExecutor(5, 5, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(5));
//使用线程池创建子线程,并在子线程获取
executorService.submit(() -> {
Map<String, String> stringMap = UserInheritableThreadLocal.get();
System.out.println(JSONObject.toJSONString(stringMap));
});
UserInheritableThreadLocal.remove();
executorService.shutdown();
}
}
# 程序输出
{"name":"tom"}
{"name":"tom"}
package com.example.threadlocalandother;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author tom
*/
public class InheritableThreadLocalDemo2 {
// 创建一个InheritableThreadLocal来存储用户ID
private static final InheritableThreadLocal<String> userIdThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
// 模拟处理用户请求
handleUserRequest("user123");
}
public static void handleUserRequest(String userId) {
// 在处理请求前,设置当前线程的用户ID
userIdThreadLocal.set(userId);
try {
// 模拟一些业务逻辑
doBusinessLogic();
// 模拟异步处理任务
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
// 在子线程中也可以访问到父线程设置的用户 ID
System.out.println("Processing task in child thread for user: " + userIdThreadLocal.get());
});
executor.shutdown();
} finally {
// 清除当前线程的用户 ID,防止内存泄漏
userIdThreadLocal.remove();
}
}
public static void doBusinessLogic() {
// 在业务逻辑中可以访问到当前线程的用户 ID
System.out.println("Processing business logic for user: " + userIdThreadLocal.get());
}
}
# 程序输出
Processing business logic for user: user123
Processing task in child thread for user: user123
需要在实际使用完毕的时候,及时调用remove方法避免内存泄漏。
inheritableThreadLocal是线程安全的吗?
package com.example.threadlocalandother;
/**
* @author tom
*/
public class InheritableThreadLocalDemo3 {
private ThreadLocal<Person> inheritablethreadlocal = new InheritableThreadLocal<>();
private void fun1() throws InterruptedException {
final Person person = new Person();
person.setName("张三");
inheritablethreadlocal.set(person);
System.out.println("fun1:" + inheritablethreadlocal.get());
final Thread t1 = new Thread(() -> {
Person p = (Person) inheritablethreadlocal.get();
p.setName("李四");
System.out.println("t1:" + inheritablethreadlocal.get());
}, "t1");
t1.start();
t1.join();
fun2();
}
private void fun2() {
System.out.println("fun2:" + inheritablethreadlocal.get());
fun3();
}
private void fun3() {
System.out.println("fun3:" + inheritablethreadlocal.get());
inheritablethreadlocal.remove();
}
public static void main(String[] args) throws InterruptedException {
InheritableThreadLocalDemo3 inheritableThreadLocalDemo3 = new InheritableThreadLocalDemo3();
inheritableThreadLocalDemo3.fun1();
}
}
class Person {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + '}';
}
}
# 程序输出
fun1:Person{name='张三'}
t1:Person{name='李四'}
fun2:Person{name='李四'}
fun3:Person{name='李四'}
如果它是线程安全的那么,在t1线程中的修改应该不能够影响到其他线程中的值,显而易见它并不是线程安全的。
解决方法是重写childValue方法,在里面做一个深拷贝就可以了。
package com.example.threadlocalandother;
/**
* @author tom
*/
public class InheritableThreadLocalDemo4 {
private ThreadLocal<Person> inheritablethreadlocal = new InheritableThreadLocal() {
@Override
protected Object childValue(Object parentValue) {
Person p = (Person) parentValue;
final Person child = new Person();
child.setName(p.getName());
return child;
}
};
private void fun1() throws InterruptedException {
final Person person = new Person();
person.setName("张三");
inheritablethreadlocal.set(person);
System.out.println("fun1:" + inheritablethreadlocal.get());
final Thread t1 = new Thread(() -> {
Person p = (Person) inheritablethreadlocal.get();
p.setName("李四");
System.out.println("t1:" + inheritablethreadlocal.get());
}, "t1");
t1.start();
t1.join();
fun2();
}
private void fun2() {
System.out.println("fun2:" + inheritablethreadlocal.get());
fun3();
}
private void fun3() {
System.out.println("fun3:" + inheritablethreadlocal.get());
inheritablethreadlocal.remove();
}
public static void main(String[] args) throws InterruptedException {
InheritableThreadLocalDemo4 inheritableThreadLocalDemo4 = new InheritableThreadLocalDemo4();
inheritableThreadLocalDemo4.fun1();
}
}
# 程序输出
fun1:Person{name='张三'}
t1:Person{name='李四'}
fun2:Person{name='张三'}
fun3:Person{name='张三'}
看起来似乎是解决了问题,结果也很美好。那么进一步思考下,我们这是在父线程给子线程传值,如果是两个不相
关的线程呢,比如说在线程池中会怎么样呢?创建线程如果是用显示创建使用inheritableThreadLocals是没有问
题。但如果是使用线程池就会有问题。
package com.example.threadlocalandother;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author tom
*/
public class InheritableThreadLocalDemo5 {
static ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
static ThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
System.out.println("主线程开始");
for (int i = 0; i < 10; i++) {
inheritableThreadLocal.set(i);
System.out.println("主线程获取值:" + inheritableThreadLocal.get());
executorService.execute(new RunnableDemo());
inheritableThreadLocal.remove();
}
executorService.shutdown();
}
private static class RunnableDemo implements Runnable {
@Override
public void run() {
System.out.println("子线程获取值:" + inheritableThreadLocal.get());
}
}
}
# 程序输出
主线程开始
主线程获取值:0
主线程获取值:1
主线程获取值:2
主线程获取值:3
主线程获取值:4
子线程获取值:0
子线程获取值:1
主线程获取值:5
子线程获取值:1
子线程获取值:0
子线程获取值:1
主线程获取值:6
子线程获取值:0
子线程获取值:0
主线程获取值:7
主线程获取值:8
子线程获取值:0
子线程获取值:1
主线程获取值:9
子线程获取值:0
按照我们上文的分析,那也结果也应该是子线程跟主线程都会输出从0~9,但是结果却大相径庭。
package com.example.threadlocalandother;
import java.util.concurrent.*;
/**
* @author tom
*/
public class InheritableThreadLocalDemo6 {
static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
static ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
static CountDownLatch firstCountDownLatch = new CountDownLatch(1);
static CountDownLatch secondCountDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws InterruptedException {
threadLocal.set(1);
executorService.execute(() -> {
System.out.println("子线程获取" + threadLocal.get());
threadLocal.remove();
System.out.println("异步任务1");
firstCountDownLatch.countDown();
});
firstCountDownLatch.await();
System.out.println("父线程获取" + threadLocal.get());
threadLocal.set(2);
executorService.execute(() -> {
System.out.println("子线程获取" + threadLocal.get());
threadLocal.remove();
System.out.println("异步任务2");
secondCountDownLatch.countDown();
});
secondCountDownLatch.await();
System.out.println("父线程获取" + threadLocal.get());
executorService.shutdown();
}
}
# 程序输出
子线程获取1
异步任务1
父线程获取1
子线程获取null
异步任务2
父线程获取2
我们新创建了一个Runnable对象放入线程池中通过execute方法执行,会首先判断线程池的核心线程数有没有达
到最大,如果还没达到最大那么新启动一个work线程,如果达到最大,那么接着判断我们给的队列是否满了,如
果还未满就入队,如果已经满了继续判断最大线程数是否达到最大,如果还未达到最大则继续新启动一个work线
程,已经达到最大就执行拒绝策略。
当核心线程数还没用完的时候,会创建新的线程,那么InheritableThreadLocal的值就会从父线程里面copy,自然
是没有问题,当我们在子线程中操作InheritableThreadLocal是可以拿到数据。
但是如果有新的任务进来(核心线程池满了,队列没有满),只要核心线程有空闲,就会复用原先创建好的核心线
程,这个时候,如果上一个使用过这个线程的子线程修改了InheritableThreadLocal,那么当前的子线程在使用
InheritableThreadLocal就会有问题了。因为这次是没有重行创建新线程,那么InheritableThreadLocal还是之前
的InheritableThreadLocal。
InheritableThreadLocal的继承性是在new Thread创建子线程时候在构造函数内把父线程内线程变量拷贝到子线
程内部的。为了不在创建新线程耗费资源,我们一般会用线程池,线程池的线程会复用,那么线程中的
ThreadLocal便不对了,可能是旧的,因为线程是旧的。
如果我们想在线程池等复用线程的组建中,使用ThreadLocal值的传递功能,来解决异步执行时上下文传递,那么
应该如何处理呢?
3、TransmittableThreadLocal介绍
JDK的InheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行
组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已
经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal值传递到 任务执行时。
TransmittableThreadLocal类继承并加强InheritableThreadLocal类,解决上述的问题。
InheritableThreadLocal为阿里开源的一个组件,所以我们在使用的时候需要添加如下依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.12.6</version>
</dependency>
示例:
package com.example.threadlocalandother;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author tom
*/
public class TransmittableThreadLocalDemo {
static ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
static ThreadLocal<Integer> inheritableThreadLocal = new TransmittableThreadLocal<>();
public static void main(String[] args) {
System.out.println("主线程开始");
for (int i = 0; i < 10; i++) {
inheritableThreadLocal.set(i);
System.out.println("主线程获取值:" + inheritableThreadLocal.get());
executorService.execute(TtlRunnable.get(new RunnableDemo()));
inheritableThreadLocal.remove();
}
executorService.shutdown();
}
private static class RunnableDemo implements Runnable {
@Override
public void run() {
System.out.println("子线程获取值:" + inheritableThreadLocal.get());
}
}
}
# 程序输出
主线程开始
主线程获取值:0
主线程获取值:1
主线程获取值:2
主线程获取值:3
子线程获取值:0
子线程获取值:1
主线程获取值:4
子线程获取值:2
子线程获取值:3
主线程获取值:5
子线程获取值:4
子线程获取值:5
主线程获取值:6
主线程获取值:7
子线程获取值:6
主线程获取值:8
子线程获取值:7
主线程获取值:9
子线程获取值:8
子线程获取值:9
这样我们就可以在子线程中正确的获取到想要的结果了。
package com.example.threadlocalandother;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import java.util.concurrent.*;
/**
* @author tom
*/
public class TransmittableThreadLocalDemo2 {
static ThreadLocal<Integer> threadLocal = new TransmittableThreadLocal<>();
static ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
static CountDownLatch firstCountDownLatch = new CountDownLatch(1);
static CountDownLatch secondCountDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws InterruptedException {
threadLocal.set(1);
executorService.execute(TtlRunnable.get(() -> {
System.out.println("子线程获取" + threadLocal.get());
threadLocal.remove();
System.out.println("异步任务1");
firstCountDownLatch.countDown();
}));
firstCountDownLatch.await();
System.out.println("父线程获取" + threadLocal.get());
threadLocal.set(2);
//就算上面线程把数据删了,而且还复用了上面的线程,也不影响。
executorService.execute(TtlRunnable.get(() -> {
System.out.println("子线程获取" + threadLocal.get());
threadLocal.remove();
System.out.println("异步任务2");
secondCountDownLatch.countDown();
}));
secondCountDownLatch.await();
System.out.println("父线程获取" + threadLocal.get());
executorService.shutdown();
}
}
# 程序输出
子线程获取1
异步任务1
父线程获取1
子线程获取2
异步任务2
父线程获取2
package com.example.threadlocalandother;
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author tom
*/
public class TransmittableThreadLocalDemo3 {
private static final TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(() -> {
threadLocal.set("Hello, World!");
try {
doBusinessLogic();
} finally {
threadLocal.remove();
}
});
executorService.submit(() -> {
try {
doBusinessLogic();
} finally {
threadLocal.remove();
}
});
executorService.shutdown();
}
public static void doBusinessLogic() {
// 在业务逻辑中可以访问到当前线程的变量值
System.out.println("Processing business logic with value: " + threadLocal.get());
}
}
# 程序输出
Processing business logic with value: Hello, World!
Processing business logic with value: null
自己实现一个类似于TransmittableThreadLocal的功能:
package com.example.threadlocalandother;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
/**
* @author tom
*/
public class TransmittableThreadLocalDemo4 {
private static ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static <T> CompletableFuture<T> invokeToCompletableFuture(Supplier<T> supplier) {
// 第一步
String context = contextHolder.get();
Supplier<T> newSupplier = () -> {
//第二步
String origin = contextHolder.get();
try {
contextHolder.set(context);
// 第三步
return supplier.get();
} finally {
// 第四步
contextHolder.set(origin);
}
};
return CompletableFuture.supplyAsync(newSupplier);
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
contextHolder.set("main");
CompletableFuture<String> context = invokeToCompletableFuture(() -> TransmittableThreadLocalDemo4.contextHolder.get());
System.out.println(context.get());
}
}
# 程序输出
main
总得来说,就是在将异步任务派发给线程池时,「对其做一下上下文传递的处理。」
第一步:主线程获取上下文,传递给任务暂存。之后的操作都将是异步执行线程操作的。
第二步:异步执行线程将原有上下文取出,暂时保存。并将主线程传递过来的上下文设置。
第三步:执行异步任务。
第四步:将原有上下文设置回去。
可以看到一般在异步线程执行完任务之后不会直接进行remove,而是一开始取出原上下文(可能为 「NULL」,
也可能是线程创建时InheritableThreadLocal 继承过来的值。当然后续也会被清除的),并在任务执行
「结束重新放回」。这样的方式可以说是异步 「ThreadLocal 传递的标准范式」
这样子既起到了显式清除主线程带来的上下文,也避免了如果线程池的拒绝策略为 CallerRunsPolicy ,后续
处理时上下文丢失的问题。