起初是在写项目过程中,在完成超时订单自动取消的任务时,使用xxl-job,整个逻辑是需要从订单表中找出过期的订单,然后将其存入订单取消表。

存入订单取消表时需要存储用户的信息。我最开始没想那么多,就直接从ThreadLocal中取出用户信息,但是后续的测试中发现用户信息为空,我就不断地调式不断地找问题。最后意识到定时任务运行在一个新的线程,不能从ThreadLocal中取出用户信息。其实那时候我知道如果线程切换就不能从ThreadLocal中取出用户信息这个问题,但是一直没往那方面去想,导致我调试了两个小时,最终意识到了这个问题。其实可以不从ThreadLocal中取用户信息的,因为从订单表查询订单的数据就含有用户的信息,然后我就利用订单中的用户信息进行后续操作了。

然后我就在想能不能有没有一种方式能使得定时任务这种新的线程可以访问ThreadLocal中的数据

有!!!但是和我项目中遇到的问题就没太大关系了

1.ThreadLocal

从字面含义上看,ThreadLocal即线程本地变量,可以依赖ThreadLocal保存线程私有的变量,主要实现思想是在每个线程中创建了⼀个副本,那么每个线程可以访问自己内部的副本变量。也就是说,同⼀个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。ThreadLocal主要包含以下特点:

A:每个线程都维护自己的实例副本,且该副本只能由当前线程访问;
B:每个线程有自己的实例副本,且其它线程不可访问,所以也就不存在多线程间共享的问题

ThreadLocal并不支持继承性,那么如何做到在父子线程之间传递呢?

2.InheritableThreadLocal

ThreadLocal不同,InheritableThreadLocal允许子线程继承父线程(在这种情况下就是主线程)中设置的变量值。当在主线程中使用线程池创建子线程时,子线程可以自动获取到主线程在InheritableThreadLocal中设置的值。

总体上来说,InheritableThreadLocal实现跨线程数据同步主要分成三个操作:

1:继承ThreadLocal重写了Map的创建获取方法;
2:线程调用InheritableThreadLocal方法获取数据时,如果发现InheritableThreadLocal还没有初始化,那么会先执行初始化操作;
3:当线程创建新的线程时,线程初始化的时候会调用init方法将当前线程中的inheritableThreadLocal变量复制到新创建线程的inheritableThreadLocal中,从而实现跨线程数据共享。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MyThreadLocalData {
private static final InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

public static void setData(String data) {
inheritableThreadLocal.set(data);
}

public static String getData() {
return inheritableThreadLocal.get();
}

public class Main {
public static void main(String[] args) {
MyThreadLocalData.setData("Main thread data");
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
executorService.submit(() -> {
System.out.println("Sub - thread got data: " + MyThreadLocalData.getData());
});
}
executorService.shutdown();
}
}
}

虽然inheritableThreadLocal可以做到跨线程数据共享,但是仅限于父子线程,而且是在线程初始化的时候完成复制。而在实际开发过程中,大多数的业务场景都是使用线程池来避免线程频繁创建销毁的开销,此时inheritableThreadLocal起不到作用。

3.TransmittableThreadLocal

TransmittableThreadLocal(TTL)是阿里巴巴开源的一个用于解决在多线程环境下,线程局部变量跨线程传递问题的工具类,它是对 Java 标准库中ThreadLocal的增强和扩展

  • TransmittableThreadLocal(TTL)是一个用于解决在多线程环境下,特别是在使用线程池时ThreadLocal值传递问题的工具类。
  • 当使用普通的ThreadLocal时,在线程池环境下,由于线程可能被复用,会导致ThreadLocal的值在不同任务之间产生混淆。而TransmittableThreadLocal能够确保在任务提交到线程池时,正确地传递ThreadLocal的值,并且在任务执行完后恢复线程的原始状态。

工作原理

  • 包装任务
    • 当向线程池提交任务时,TransmittableThreadLocal对任务进行包装。例如,在 Java 中,如果使用ExecutorService提交一个Runnable任务,TransmittableThreadLocal会将这个Runnable包装成一个新的Runnable,这个新的Runnable会在执行前获取当前线程(提交任务的线程)中的TransmittableThreadLocal值,并将这些值传递到执行任务的线程中。
  • 值的传递与恢复
    • 在任务执行过程中,执行任务的线程可以像使用普通ThreadLocal一样使用TransmittableThreadLocal,获取到从提交任务的线程传递过来的值。当任务执行完毕后,TransmittableThreadLocal会将执行任务的线程的状态恢复到执行任务之前的状态,避免对线程池中的线程状态造成持久的影响。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class TTLExample {
private static final TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>();

public static void main(String[] args) {
ttl.set("Main thread value");
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(() -> {
System.out.println("Sub - thread got value: " + ttl.get());
});
ttl.remove();
executorService.shutdown();
}
}