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

news/2024/9/20 6:59:56/ 标签: java

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

1、ThreadLocal介绍

spring 中基于 ThreadLocal 来实现事务。

多线程 访问同一个共享变量的时候容易出现并发问题,ThreadLocal是除了加锁这种同步方式之外的一种保证

规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是

线程自己的变量这样就不会存在线程不安全问题。在实际多线程操作的时候,操作的是自己本地内存中的变量,从

而规避了线程安全问题。

ThreadLocal又叫做线程本地变量是为每一个Thread创建的一个变量的副本,每个线程都可以在内部访问到这个副

本。通过这种方式我们在一个线程的生命周期以内安全的访问这个变量,不用担心被其他线程所污染。这是它相对

于全局变量所带来的优势。

java">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方法。

java">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 可能会有内存泄漏的风险!

java">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
java">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
java">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。

java">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线程中能够正确的获取到结果。

java">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」**变量中。

java">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"}
java">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是线程安全的吗?

java">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方法,在里面做一个深拷贝就可以了。

java">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是没有问

题。但如果是使用线程池就会有问题。

java">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,但是结果却大相径庭。

java">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>

示例:

java">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

这样我们就可以在子线程中正确的获取到想要的结果了。

java">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
java">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的功能:

java">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 ,后续

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


http://www.ppmy.cn/news/1518911.html

相关文章

【bug记录7】导入Lottie的json格式动画,获取不到相对路径下的图片

一、问题背景 在vue3项目中&#xff0c;想把Lottie依赖的图片放在其相对路径下&#xff0c;但是发现即使修改其中的u参数&#xff0c;也无法拿到其相对路径中的图片。因为json解析绝对路径&#xff0c;只能将图片放在项目根目录下的public文件夹应急。 二、解决方法 将Lottie…

Lua 代码编码规范

lua代码格式 vscode stylua 插件 配置文件stylua.toml column_width 240 line_endings “Unix” indent_type “Spaces” --使用空格 很重要&#xff0c;保证不同编辑器打开是一样的 indent_width 4 quote_style “AutoPreferDouble” --字符串引号样式双引号 call_paren…

c++关于字符串的练习

提示并输入一个字符串&#xff0c;统计该字符串中字母个数、数字个数、空格个数、其他字符的个数 #include <iostream> #include<string> using namespace std;int main() {string s1;int letter0,digit0,space0,other0;cout<<"请输入一个字符串:"…

ISO C++ 和 GNU C++ 的区别

C 的 ios 标准和 gnu 标准是两种编译器标准或模式&#xff0c;主要由编译器在编译 C 代码时所遵循的规范决定。它们之间的区别主要在于是否包含标准之外的扩展以及对特定功能的支持。 1. ISO C 标准 (-stdc11, -stdc14, -stdc17, 等) 定义: ISO C 标准是由国际标准化组织 (IS…

3D打印透气钢与传统透气钢的差异

透气钢作为一种集金属强度与透气性能于一体的特殊材料&#xff0c;在注塑模具领域扮演着关键角色&#xff0c;通过有效排除模具内困气&#xff0c;显著提升制品成型质量与生产效率。当前&#xff0c;市场上主流的透气钢产品多源自日本、美国&#xff0c;其高昂成本与技术壁垒限…

Java算法之选择排序(Selection Sort)

简介 选择排序是一种简单直观的排序算法&#xff0c;它的工作原理是每次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;然后放到序列的起始位置。通过重复这个过程&#xff0c;直到所有元素都被排序。 算法步骤 在未排序序列中找到最小…

【Vue】Echart图表中的属性

目录 背景属性介绍1. title2. tooltip3. legend4. toolbox5. color6. grid7. xAxis / yAxis8. series9. visualMap10. dataZoom 示例 背景 最近Echart用的比较多&#xff0c;改动的展示效果其实也就那么些&#xff0c;而且很多案例、展示效果在Echart官网写的都很丰富&#xf…

数字化转型中的数据应用:挑战、机遇与追赶之路

在数字化时代的大潮中&#xff0c;数据已悄然从企业的边缘资源跃升为最宝贵的核心资产。然而&#xff0c;这场数据盛宴并未带来普遍的数据应用成熟&#xff0c;反而揭示了企业在数据利用上的巨大鸿沟。即便是全球500强企业&#xff0c;在数据应用的征途上&#xff0c;也仅仅是比…

android 14及android15 READ_EXTERNAL_STORAGE跟相册,视频权限的适配

最近在做Android15的适配&#xff0c;发现WRITE_EXTERNAL_STORAGE跟READ_EXTERNAL_STORAGE无法使用了&#xff0c;被弃用了 在android 13添加了外部细分权限&#xff0c;READ_MEDIA_IMAGES跟READ_MEDIA_VIDEO及 READ_MEDIA_AUDIO权限&#xff0c;而在应用内部的文件管理则不需要…

TCP与UDP对比

这两个都是运输层的协议&#xff0c;UDP是无连接不可靠的&#xff0c;而TCP是面向连接可靠的&#xff0c;相较而言&#xff0c;UDP要简单许多。两者对比做一个简要概述。 连接方式 1.UDP是无连接的&#xff0c;就是通信双方无需建立连接就可以随时发送数据。 2.而TCP在发送数…

基于Python的机器学习系列(18):梯度提升分类(Gradient Boosting Classification)

简介 梯度提升&#xff08;Gradient Boosting&#xff09;是一种集成学习方法&#xff0c;通过逐步添加新的预测器来改进模型。在回归问题中&#xff0c;我们使用梯度来最小化残差。在分类问题中&#xff0c;我们可以利用梯度提升来进行二分类或多分类任务。与回归不同&#xf…

2024.8.31 Python,合并区间,用sort通过列表第一个元素给列表排序,三数之和,跳跃游戏

1.合并区间 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a; 输入&#xff1a;inter…

ARCGIS 纸质小班XY坐标转电子要素面(2)

本章用于说明未知坐标系情况下如何正确将XY转要素面 背景说明 现有资料&#xff1a;清除大概位置&#xff0c;纸质小班图&#xff0c;图上有横纵坐标&#xff0c;并已知小班XY拐点坐标&#xff0c;但未知坐标系。需要上图 具体操作 大部分操作同这边文章ARCGIS 纸质小班XY…

Java | Leetcode Java题解之第387题字符串中的第一个唯一字符

题目&#xff1a; 题解&#xff1a; class Solution {public int firstUniqChar(String s) {Map<Character, Integer> position new HashMap<Character, Integer>();Queue<Pair> queue new LinkedList<Pair>();int n s.length();for (int i 0; i …

模型 错位竞争(战略规划)

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。与其更好&#xff0c;不如不同。 1 错位竞争的应用 1.1 美团的错位竞争策略 美团&#xff0c;作为中国领先的电子商务平台&#xff0c;面临着阿里巴巴等电商巨头的竞争压力。为了在市场中获得独特的…

MATLAB虫害检测预警系统

一、课题介绍 本课题是基于MATLAB颜色的植物虫害检测识别&#xff0c;可以辨析植物叶子属于是轻度虫害&#xff0c;中度虫害&#xff0c;严重虫害&#xff0c;正常等四个级别。算法流程&#xff1a;每种等级叶子分别放在同一个文件夹&#xff0c;训练得到每个文件夹每个叶…

续:MySQL的gtid模式

为什么要启用gtid? master端和slave端有延迟 ##设置gtid master slave1 slave2 [rootmysql1 ~]# vim /etc/my.cnf [rootmysql1 ~]# cat /etc/my.cnf [mysqld] datadir/data/mysql socket/data/mysql/mysql.sock symbolic-links0 log-binmysql-bin server-id1 slow_query_lo…

AI学习指南深度学习篇-门控循环单元中的门控机制

AI学习指南深度学习篇-门控循环单元中的门控机制 引言 深度学习是当前人工智能领域的一个重要方向&#xff0c;而循环神经网络&#xff08;RNN&#xff09;在处理序列数据方面展现出了强大的能力。然而&#xff0c;标准的RNN在处理长序列时存在长期依赖问题&#xff0c;容易导…

jenkins安装k8s插件发布服务

1、安装k8s插件 登录 Jenkins&#xff0c;系统管理→ 插件管理 → 搜索 kubernetes&#xff0c;选择第二个 Kubernetes&#xff0c;点击 安装&#xff0c;安装完成后重启 Jenkins 。 2、对接k8s集群、申请k8s凭据 因为 Jenkins 服务器在 kubernetes 集群之外&#xff0c;所以…

oracle11g常用基本字典和动态性能字典

文章目录 Oracle11g的动态性能视图1、动态性能视图&#xff1a;2、常用的Oracle 11g动态性能视图&#xff1a;V$SESSION&#xff1a;V$SQL&#xff1a;V$SQL_PLAN&#xff1a;V$SYSSTAT&#xff1a;V$SQLSTAT&#xff1a;V$SESSION_EVENT&#xff1a;3、基本数据字典4、动态性能…