ThreadLocal扩展-在子线程中如何使用父线程在ThreadLocal中的内容

在项目中经常有一种场景,用户在登录之后使用,ThreadLocal存储用户的登录态,方便在业务层获取用户信息,userId等

但是在子线程中无法获得到父线程在ThreadLocal存放的内容,因为ThreadlLocal是线程局部变量,只有在当前线程能获取到当前线程在ThreadLocal中的内容

java
复制代码
package com.hangcheng.statistics.controller.exam; import cn.hutool.core.thread.ThreadUtil; public class Test { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { // 主线程 将值放入ThreadLocal中 threadLocal.set("ThreadLocal"); new Thread(() -> { // 子线程从ThreadLocal中获取值 System.out.println(threadLocal.get()); }).start(); ThreadUtil.sleep(100000); System.out.println("主线程结束"); } }

控制台输出

java
复制代码
null 主线程结束

可以看到子线程获取不到 主线程的ThreadLocal

解决方法:

1.使用阿里开源TransmittableThreadLocal

gitee地址:

https://gitee.com/alibaba-projects/transmittable-thread-local

pom:

java
复制代码
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.12.0</version> </dependency>

示例:

java
复制代码
package com.hangcheng.statistics.controller.exam; import cn.hutool.core.thread.ThreadUtil; import com.alibaba.ttl.TransmittableThreadLocal; public class Test { private static final TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>(); public static void main(String[] args) { // 主线程 将值放入ThreadLocal中 threadLocal.set("ThreadLocal"); new Thread(() -> { // 子线程从ThreadLocal中获取值 System.out.println(threadLocal.get()); }).start(); ThreadUtil.sleep(10000); System.out.println("主线程结束"); } }

控制台输出

java
复制代码
ThreadLocal 主线程结束

可以看到 子线程 获取到 主线程在ThreadLocal中的内容

如果使用线程池,因为线程池中的线程是复用的,就会出现以下问题

java
复制代码
package com.hangcheng.statistics.controller.exam; import cn.hutool.core.thread.ThreadUtil; import com.alibaba.ttl.TransmittableThreadLocal; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Test { private static final TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>(); // 5个核心线程数 保证 线程1使用后 线程2 不创建新的线程 继续使用线程1 创建的线程 private static final Executor executor = new ThreadPoolExecutor(5, 5, 1L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>() ); public static void main(String[] args) { Thread thread1 = new Thread("线程1") { @Override public void run() { threadLocal.set("线程1set"); String name = Thread.currentThread().getName(); for (int i = 0; i < 5; i++) { executor.execute(() -> { System.out.println(name + ":" + threadLocal.get()); }); } } }; Thread thread2 = new Thread("线程2") { @Override public void run() { threadLocal.set("线程2set"); String name = Thread.currentThread().getName(); for (int i = 0; i < 5; i++) { executor.execute(() -> { System.out.println(name + ":" + threadLocal.get()); }); } } }; thread1.start(); ThreadUtil.sleep(20000); thread2.start(); ThreadUtil.sleep(100000); System.out.println("主线程结束"); } }

控制台输出:

java
复制代码
线程1:线程1set 线程1:线程1set 线程1:线程1set 线程1:线程1set 线程1:线程1set 线程2:线程1set 线程2:线程1set 线程2:线程1set 线程2:线程1set 线程2:线程1set

可以看到线程2获取到了线程1存放在ThreadLocal中的内容,因为线程1在线程池中创建了五个线程,线程在线程池中是复用的,所以在线程2继续复用线程池中的线程,就会导致线程2获取到线程1的内容

2.将线程池封装成TtlExecutors

java
复制代码
package com.hangcheng.statistics.controller.exam; import cn.hutool.core.thread.ThreadUtil; import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.threadpool.TtlExecutors; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Test { private static final TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>(); // 5个核心线程数 保证 线程1使用后 线程2 不创建新的线程 继续使用线程1 创建的线程 // 使用alibaba的TtlExecutors.getTtlExecutor 保证线程通信的可见度 private static final Executor executor = TtlExecutors.getTtlExecutor(new ThreadPoolExecutor(5, 5, 1L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>() )); public static void main(String[] args) { Thread thread1 = new Thread("线程1") { @Override public void run() { threadLocal.set("线程1set"); String name = Thread.currentThread().getName(); for (int i = 0; i < 5; i++) { executor.execute(() -> { System.out.println(name + ":" + threadLocal.get()); }); } } }; Thread thread2 = new Thread("线程2") { @Override public void run() { threadLocal.set("线程2set"); String name = Thread.currentThread().getName(); for (int i = 0; i < 5; i++) { executor.execute(() -> { System.out.println(name + ":" + threadLocal.get()); }); } } }; thread1.start(); ThreadUtil.sleep(20000); thread2.start(); ThreadUtil.sleep(100000); System.out.println("主线程结束"); } }

控制台输出:

java
复制代码
线程1:线程1set 线程1:线程1set 线程1:线程1set 线程1:线程1set 线程1:线程1set 线程2:线程2set 线程2:线程2set 线程2:线程2set 线程2:线程2set 线程2:线程2set

经过以上测试,得出TransmittableThreadLocal是在父线程创建子线程的时候,将父线程的ThreadLocal放到子线程中,但是因为线程池中的线程是复用的,所以就会导致线程1在线程池中创建的线程,线程2继续复用线程1创建的线程,然后线程2在使用线程池的时候就会读取到不属于当前线程(线程2)的ThreadLocal,这个使用TtlExecutors封装一下线程池就解决这个问题

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP