线程间数据传递之ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal

本文主要是介绍线程间数据传递之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() {@Overridepublic 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() {@Overridepublic void run() {// 设置线程1中本地变量的值LOCAL_VARIABLE.set(100);print("Thread 1");System.out.println("After remove: " + LOCAL_VARIABLE.get());}});Thread t2 = new Thread(new Runnable() {@Overridepublic 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();}@Overridepublic 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() {@Overridepublic void run() {//子线程同时去获取自己的inheritableThreadLocalsInteger 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来存储用户IDprivate static final InheritableThreadLocal<String> userIdThreadLocal = new InheritableThreadLocal<>();public static void main(String[] args) {// 模拟处理用户请求handleUserRequest("user123");}public static void handleUserRequest(String userId) {// 在处理请求前,设置当前线程的用户IDuserIdThreadLocal.set(userId);try {// 模拟一些业务逻辑doBusinessLogic();// 模拟异步处理任务ExecutorService executor = Executors.newSingleThreadExecutor();executor.submit(() -> {// 在子线程中也可以访问到父线程设置的用户 IDSystem.out.println("Processing task in child thread for user: " + userIdThreadLocal.get());});executor.shutdown();} finally {// 清除当前线程的用户 ID,防止内存泄漏userIdThreadLocal.remove();}}public static void doBusinessLogic() {// 在业务逻辑中可以访问到当前线程的用户 IDSystem.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;}@Overridepublic 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() {@Overrideprotected 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 {@Overridepublic 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 {@Overridepublic 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 ,后续

处理时上下文丢失的问题。

这篇关于线程间数据传递之ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1119271

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X

pandas数据过滤

Pandas 数据过滤方法 Pandas 提供了多种方法来过滤数据,可以根据不同的条件进行筛选。以下是一些常见的 Pandas 数据过滤方法,结合实例进行讲解,希望能帮你快速理解。 1. 基于条件筛选行 可以使用布尔索引来根据条件过滤行。 import pandas as pd# 创建示例数据data = {'Name': ['Alice', 'Bob', 'Charlie', 'Dav