ThreadLocal
线程私有,无法跨线程传递
示例:在线程0中为threadLocal
赋值,看另一个线程1和主线程能否获取threadLocal
public class ThreadTest {
//线程私有,无法传递
private final static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
//线程0
Runnable runnable1 = () -> {
threadLocal.set("threadLocal");
System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());
};
new Thread(runnable1).start();
//线程1
Runnable runnable2 = () -> System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());
new Thread(runnable2).start();
try {
Thread.sleep(1000);
//主线程
System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
Thread-0:threadLocal
Thread-1:null
main:null
- 执行
main
方法的是父线程,它创建了两个子线程:线程0与线程1 - 线程0赋予
ThreadLocal
一个值,则为线程0私有,其他线程拿不到。
InheritableThreadLocal
线程私有,但可以由父线程传递给子线程。继承于ThreadLocal
示例:在主线程中为threadLocal
与InheritableThreadLocal
赋值,看哪个能被其他线程获取
//线程私有,无法传递
private final static ThreadLocal<String> threadLocal = new ThreadLocal<>();
//线程私有,但可以由父线程传递给子线程
private final static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
inheritableThreadLocal.set("inheritableThreadLocal");
threadLocal.set("threadLocal");
//线程0,输出threadLocal
Runnable runnable1 = () -> {
System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());
};
new Thread(runnable1).start();
//线程1,输出inheritableThreadLocal
Runnable runnable2 = () -> System.out.println(Thread.currentThread().getName()+":"+inheritableThreadLocal.get());
new Thread(runnable2).start();
try {
Thread.sleep(1000);
//主线程
System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());
System.out.println(Thread.currentThread().getName()+":"+inheritableThreadLocal.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
结果:
Thread-0:null
Thread-1:inheritableThreadLocal
main:threadLocal
main:inheritableThreadLocal
- 因为
ThreadLocal
无法跨线程传递,所以线程0无法获取主线程赋值的ThreadLocal
- 因为
InheritableThreadLocal
可以由父线程传递给子线程(线程0与线程1是在主线程创建的,所以主线程是他们的父线程),所以线程1可以获取InheritableThreadLocal
- 因为
ThreadLocal
是主线程赋值的,所以主线程可以获取 - 因为
InheritableThreadLocal
是主线程赋值的,所以主线程也可以获取,并可传递给子线程
但是
InheritableThreadLocal
有一个问题
在使用线程池时,线程可能会被重用。比如:
- 父线程赋予
InheritableThreadLocal
一个值,并从线程池拿出一些线程作为子线程运行任务,使用了这个值; - 在中途,父线程修改了
InheritableThreadLocal
的值,再从线程池拿出一些线程运行任务,由于线程池复用,此时的子线程可能是1中释放的,所以用到的可能不是新修改的InheritableThreadLocal
值。
执行以下代码
创建一个线程池,设置其固定大小为1,调用这个线程池两次,在此之前分别对主线程中的InheritableThreadLocal
进行赋值操作,观察运行的结果。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadTest {
public static InheritableThreadLocal<String> inheritableThreadlocal = new InheritableThreadLocal<>();
private static ExecutorService executorservice = Executors.newFixedThreadPool(1);
public static void main(String args[]) throws Exception{
inheritableThreadlocal.set("主线程赋值1");
executorservice.submit(new Runnable(){
@Override
public void run() {
System.out.println("子线程-01获取本地变量值: "
+ Thread.currentThread().getName()+"::"+inheritableThreadlocal.get());
}
});
Thread.sleep(1000);
inheritableThreadlocal.set("主线程赋值2");
executorservice.submit(new Runnable(){
@Override
public void run() {
System.out.println("子线程-02获取本地变量值: "
+ Thread.currentThread().getName()+"::"+inheritableThreadlocal.get());
}
});
executorservice.shutdown( );
}
}
结果:
子线程-01获取本地变量值: pool-1-thread-1::主线程赋值1
子线程-02获取本地变量值: pool-1-thread-1::主线程赋值1
两次调用获取的值一致,子线程-02并没有获取到inheritableThreadlocal
的新值。
这是因为线程池是复用线程的,第一个线程01结束后,创建的02线程是对01的复用,从名字也可以看出,二者是同一个线程,而01得到了从父线程传递的inheritableThreadlocal
为**“主线程赋值1”**,所以02获取的与01是同一个值。要解决此问题,需要使用TransmittableThreadLocal
TransmittableThreadLocal
阿里出品,继承于
InheritableThreadLocal
,解决在线程池复用线程导致的上下文相同问题
代码示例:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
需要注意的是,在使用TransmittableThreadLocal
时,我们需要使用com.alibaba.ttl.TtlRunnable
或com.alibaba.ttl.TtlCallable
来对任务进行包装,以确保线程池中的线程能正确传递ThreadLocal
的值。
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadTest {
private static ExecutorService executorservice = Executors.newFixedThreadPool(1);
private static TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<String>();
public static void main(String args[]) throws Exception{
transmittableThreadLocal.set("主线程赋值1");
executorservice.submit(TtlRunnable.get(new Runnable(){
@Override
public void run() {
System.out.println("子线程-01获取本地变量值: "
+ Thread.currentThread().getName()+"::"+transmittableThreadLocal.get());
}
}
));
Thread.sleep(1000);
transmittableThreadLocal.set("主线程赋值2");
executorservice.submit(TtlRunnable.get(new Runnable(){
@Override
public void run() {
System.out.println("子线程-02获取本地变量值: "
+ Thread.currentThread().getName()+"::"+transmittableThreadLocal.get());
}
}
));
executorservice.shutdown( );
}
}
结果:
子线程-01获取本地变量值: pool-1-thread-1::主线程赋值1
子线程-02获取本地变量值: pool-1-thread-1::主线程赋值2
SpringBoot应用实例
1.线程池
上面使用了
TtlRunnable
或TtlCallable
,这里不用,但需要配置TtlExecutors
包装线程池。为了展现复用场景,将所有参数设置为1
import com.alibaba.ttl.threadpool.TtlExecutors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1); // 核心线程数1
executor.setMaxPoolSize(1); // 最大线程数1
executor.setQueueCapacity(1); // 队列容量1,如果此数字大于0,使用队列LinkedBlockingQueue
executor.setThreadNamePrefix("AsyncThread-"); // 线程名称前缀
executor.initialize();//初始化线程池,是以上参数生效,最好放到最后
//需要使用ttl线程池包装,否则`TransmittableThreadLocal`不生效
return TtlExecutors.getTtlExecutorService(executor.getThreadPoolExecutor());
}
}
com.alibaba.ttl.threadpool.
TtlExecutors
与ThreadPoolTaskExecutor
区别
功能和目的:
TtlExecutors
:com.alibaba.ttl.threadpool.TtlExecutors
是阿里巴巴开源的一个库,用于在多线程环境中传递ThreadLocal变量。它提供了一系列的静态方法,可以将原始的线程池包装为一个支持ThreadLocal传递的线程池。ThreadPoolTaskExecutor
:ThreadPoolTaskExecutor
是Spring框架提供的一个实现了Executor
和AsyncTaskExecutor
接口的线程池类。它提供了更多的功能和配置选项,例如线程池的大小、拒绝策略、线程池的初始化和销毁等。
支持的功能:
TtlExecutors
:TtlExecutors
主要用于解决在多线程环境下,使用ThreadLocal
会导致值在线程池中传递不正确的问题。它通过包装原始的线程池,确保在任务执行时,线程池中的ThreadLocal
值能正确地传递。ThreadPoolTaskExecutor
:ThreadPoolTaskExecutor
提供了更多的功能和配置选项,例如线程池大小的调整、任务队列的选择、拒绝策略的配置等。
使用场景:
TtlExecutors
:TtlExecutors
适用于在多线程环境下,需要在线程池中传递ThreadLocal
值的场景,例如在跨线程执行任务时,需要保持一些上下文信息的一致性。ThreadPoolTaskExecutor
:ThreadPoolTaskExecutor
适用于常规的线程池管理场景,可以配置线程池的各种属性和行为。
需要根据具体的需求来选择适合的线程池管理类。如果需要在多线程环境下传递ThreadLocal
值,可以考虑使用TtlExecutors
。如果需要更多的功能和配置选项,可以使用ThreadPoolTaskExecutor
。
2.ThreadLocal工具类
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
import com.alibaba.ttl.TransmittableThreadLocal;
public class ThreadTool {
//清除threadlocal中的值
public static void clear() {
threadlocal.remove();
}
public static String getThreadlocal() {
return threadlocal.get();
}
public static void setThreadlocal(String threadlocalParam) {
threadlocal.set(threadlocalParam);
}
public static TransmittableThreadLocal<String> threadlocal = new TransmittableThreadLocal<String>();
}
3.异步方法
注意
@Async
注解或有多种失效情况,要避免;指定
@Async
异步使用上面配置的线程池,括号中添加线程池名字。
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class TtlTool {
@Async("asyncExecutor")
public void ttl1(Integer i) throws InterruptedException{
System.out.println("第"+i+"个异步方开始法执行");
System.out.println("第"+i+"个异步方法的线程:"+Thread.currentThread().getName()+" 的threadlocal值为:" + ThreadTool.getThreadlocal());
//ThreadTool.clear();
System.out.println();
System.out.println();
}
}
4.Controller调用
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private TtlTool ttlTool;
//localhost:8080/user/ttl
@RequestMapping("/ttl")
public void ttl() throws InterruptedException{
ThreadTool.setThreadlocal("threadlocal1");
System.out.println(Thread.currentThread().getName()+" : " + ThreadTool.getThreadlocal());
//第一次执行异步
int i = 1;
ttlTool.ttl1(i);
//等3秒,让上一个线程执行完毕后,释放回线程池,以便在下面复用
Thread.sleep(3000);
//中途修改父线程threadlocal值
ThreadTool.setThreadlocal("threadlocal2");
System.out.println(Thread.currentThread().getName()+" : " + ThreadTool.getThreadlocal());
//第二次执行异步
i = 2;
ttlTool.ttl1(i);
}
}
5.执行结果
http-nio-8080-exec-3 : threadlocal1
第1个异步方开始法执行
第1个异步方法的线程:AsyncThread-1 的threadlocal值为:threadlocal1
http-nio-8080-exec-3 : threadlocal2
第2个异步方开始法执行
第2个异步方法的线程:AsyncThread-1 的threadlocal值为:threadlocal2
结果分析:
父线程
1.打印 :
http-nio-8080-exec-3 : threadlocal1
http-nio-8080-exec-3 : threadlocal2
一是证明http-nio-8080-exec-3是执行Controller方法的线程,二是证明证明父线程的threadlocal
值修改成功。
2.打印 :
第1个异步方法的线程:`AsyncThread-1 的threadlocal`值为:threadlocal1
此打印结果证明执行Controller方法的线程http-nio-8080-exec-3是父线程,因为threadlocal1
是在Controller方法中设置的,却被异步方法得到,证明存在父子线程传递,所以http-nio-8080-exec-3是父线程,被调用的异步方法执行者是子线程
2.执行过程
收到请求后,开启一个线程先执行Controller方法,并设置threadlocal
值为threadlocal1。
然后执行第一个异步任务,因为异步任务是在Controller方法中调用的,所以执行Controller方法的线程是异步任务的父线程,则threadlocal1传递到了第一个异步任务,打印结果:
第1个异步方法的线程:`AsyncThread-1 的threadlocal`值为:threadlocal1
第一个异步任务执行完后,等待3秒,执行该异步任务的线程会回归线程池,此时修改父线程的threadlocal
值为threadlocal2,在执行第二个异步任务,打印结果:
第2个异步方法的线程:AsyncThread-1 的threadlocal值为:threadlocal2
两个打印结果的线程名相同,都是AsyncThread-1,证明确实复用了一个线程。
而threadlocal1与threadlocal2的不同值,证明TransmittableThreadLocal
成功解决了在线程池复用线程导致的上下文相同问题
ThreadLocalRandom
多线程环境下生成随机数
原Random的使用:
public class RandomTest {
public static void main(String[] args) {
// 创建一个默认种子的随机数生成器
Random random = new Random();
for (int i = 0; i < 10; i++) {
// random.nextInt(5) 输出一个[0,5)之间的随机数
System.out.println(random.nextInt(5));
}
}
}
每个Random实例里面都有一个原子性的种子变量用来记录当前的种子值。在多线程下使用单个Random实例生成随机数时,多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,这会降低并发性能,所以 ThreadLocalRandom 应运而生。
ThreadLocalRandom的使用:
在多线程环境下使用
import java.util.concurrent.ThreadLocalRandom;
public class ThreadLocalRandomTest {
public static void main(String[] args) {
// 获取一个随机数生成器
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
for (int i = 0; i < 10; i++) {
// threadLocalRandom.nextInt(5) 输出一个[0,5)之间的随机数
System.out.println(threadLocalRandom.nextInt(5));
}
}
}
使用线程池的ThreadLocal传递问题
如何解决线程池中的值传递功能?
- 多个线程之间竞争同一个变量,为了线程安全进行值隔离,可以使用
ThreadLocal
。 - 父子线程之间的值传递,可以使用
InheritableThreadLocal
类来实现。
在遇到线程池等会池化复用线程的执行组件情况下,子父线程上下文会重复,上述两种方案都会失灵。
解决方案一
通过
TransmittableThreadLocal
类 +TtlRunnable
来实现。
TtlRunnable
是阿里巴巴开源的com.alibaba.ttl.threadpool.TtlRunnable
类的简称。它是一个用于在多线程环境中传递ThreadLocal
变量的Runnable
的包装类。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadTest {
private static ExecutorService executorservice = Executors.newFixedThreadPool(1);
private static TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<String>();
public static void main(String args[]) throws Exception{
transmittableThreadLocal.set("主线程赋值1");
executorservice.submit(TtlRunnable.get(new Runnable(){
@Override
public void run() {
System.out.println("子线程-01获取本地变量值: "
+ Thread.currentThread().getName()+"::"+transmittableThreadLocal.get());
}
}
));
Thread.sleep(1000);
transmittableThreadLocal.set("主线程赋值2");
executorservice.submit(TtlRunnable.get(new Runnable(){
@Override
public void run() {
System.out.println("子线程-02获取本地变量值: "
+ Thread.currentThread().getName()+"::"+transmittableThreadLocal.get());
}
}
));
executorservice.shutdown( );
}
}
结果:
子线程-01获取本地变量值: pool-1-thread-1::主线程赋值1
子线程-02获取本地变量值: pool-1-thread-1::主线程赋值2
解决方案二
通过
TransmittableThreadLocal
类 +TtlCallable
来实现。
TtlCallable
是阿里巴巴开源的com.alibaba.ttl.threadpool.TtlCallable
类的简称。它是一个用于在多线程环境中传递ThreadLocal
变量的Callable
的包装类。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlCallable;
import com.alibaba.ttl.TtlRunnable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadTest {
private static ExecutorService executorservice = Executors.newFixedThreadPool(1);
private static TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<String>();
public static void main(String args[]) throws Exception{
transmittableThreadLocal.set("set parent value");
//用TtlCallable包装Callable
Callable<String> ttlCallable1 = TtlCallable.get(new Callable<String>() {
@Override
public String call() throws Exception {
//读取父线程中的值,其值为:set parent value
System.out.println("线程1 "+transmittableThreadLocal.get());
return "call方法返回值";
}
});
Future<String> future = executorservice.submit(ttlCallable1);
//读取当前线程中的值,其值为:set parent value
System.out.println("父线程 "+transmittableThreadLocal.get());
Thread.sleep(1000);
Callable<String> ttlCallable2 = TtlCallable.get(new Callable<String>() {
@Override
public String call() throws Exception {
//读取父线程中的值,其值为:set parent value
System.out.println("线程2 "+transmittableThreadLocal.get());
return "call方法返回值";
}
});
Future<String> futuree = executorservice.submit(ttlCallable2);
//读取当前线程中的值,其值为:set parent value
System.out.println("父线程 "+transmittableThreadLocal.get());
executorservice.shutdown();
}
}
结果:
父线程 set parent value
线程1 set parent value
父线程 set parent value
线程2 set parent value
解决方案三
通过
TransmittableThreadLocal
类 +TtlExecutors
来实现。这种不需要使用
TtlRunnable
与TtlCallable
了
com.alibaba.ttl.threadpool.TtlExecutors
是阿里巴巴开源的一个库,用于在多线程环境中传递ThreadLocal变量。它提供了一系列的静态方法,可以将原始的线程池包装为一个支持ThreadLocal传递的线程池。
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.alibaba.ttl.threadpool.TtlExecutors;
public class ThreadTest {
public static TransmittableThreadLocal<String> threadlocal = new TransmittableThreadLocal<String>();
public static void main(String[] args) throws InterruptedException{
// 创建一个普通的线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 使用TtlExecutors包装线程池
ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(executorService);
threadlocal.set("threadlocal1");
// 提交一个任务
ttlExecutorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程1 " + threadlocal.get());
}
});
Thread.sleep(1000);
threadlocal.set("threadlocal2");
// 提交一个任务
ttlExecutorService.submit(new Runnable() {
@Override
public void run() {
//threadlocal.set("threadlocal2");
System.out.println("线程2 " + threadlocal.get());
}
});
// 关闭线程池
ttlExecutorService.shutdown();
}
}