前言
本文主要是用实际案例比较下ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal 使用方式,三者区别以及原理简单介绍,避免同学们在实际开发中踩坑。
ThreadLocal
ThreadLocal 每个线程可以拥有自己线程的变量实例,可以从隔离的角度解决变量线程安全的问题。
使用场景
- 通常用于保存线程不安全的工具类,典型的需要使用的类就是 SimpleDateFormat。
- 每个线程内需要保存类似于全局变量的信息(例如在拦截器中获取的用户信息),保存一些业务内容(用户权限信息、从用户系统获取到的用户名、用户ID 等),这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的。
- 线程级别缓存,常用于数据量比较大的缓存数据,但每个线程数据不一样 又要避免被全局共享。
- 链路追踪,统计全链路耗时(常用的全链路分析框架 如鹰眼 zipkin底层原理就是这个)
使用以及出现的问题
例1
注意箭标位置
输出结果 ,没有任何问题!!!
例2
此时我把箭头代码,移到主线程上 看看会发生什么
源码如下:
public class MyTest {
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("abc");
MyThread myThread = new MyThread();
Thread t = new Thread(myThread);
t.setName("ThreadLocal-Test");
t.start();
}
static class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
}
}
}
输出结果 ???null???
总结
说明ThreadLocal没有解决主线程和子线程数据共享问题。
实现原理(简介)
ThreadLocal 是每个 Thread 都绑定一个 Map,线程之间不会互相干扰,这个Map 不是普通的 Map而是一个定制的Map:ThreadLocalMap。这个Map 使用了使用“开放寻址法”中最简单的“线性探测法”解决散列冲突问题。这点是和我们平常使用的普通的 HashMap 不一样的。其中还是用了一个神奇的数字 HASH_INCREMENT= 0x61c88647,保证了一个完美的散列分布。
InheritableThreadLocal
前面说到ThreadLocal 不支持子线程,那我们再来看看InheritableThreadLocal这个ThreadLocal升级版
例3
源码如下:
public class MyTest {
static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("abc");
MyThread myThread = new MyThread();
Thread t = new Thread(myThread);
t.setName("ThreadLocal-Test");
t.start();
}
static class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
}
}
}
输出结果 ,没问题,好像解决了呀!!!
例4
我们再来看看下一个示例。注意看 我们换成了线程池的方式,中间将 “abc"改为了"def”
源码如下:
import java.util.concurrent.*;
public class MyTest {
static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
threadLocal.set("abc");
MyThread myThread1 = new MyThread();
executorService.execute(myThread1);
Thread.sleep(100L);
threadLocal.set("def");
MyThread myThread2 = new MyThread();
executorService.execute(myThread2);
}
static class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
}
}
}
输出结果 ??? 我主线程改了"def" 为啥没有生效???
总结
说明InheritableThreadLocal 主线程与线程池的数据共享也存在问题。
实现原理(简介)
父Thread的inheritableThreadLocals 变量 copy 了一份给自己。同样借助 ThreadLocalMap 子线程可以获取到父线程的所有变量。缺点就是 Thread的 init 方法是在线程构造方法中 copy的,也就是在线程复用的线程池中是没有办法使用的。
TransmittableThreadLocal
好了 前面说InheritableThreadLocal不支持线程池,那么我们再来看看究极版的TransmittableThreadLocal
例5
源码如下:
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import java.util.Objects;
import java.util.concurrent.*;
public class MyTest {
static TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
threadLocal.set("abc");
MyThread myThread1 = new MyThread();
executorService.execute(Objects.requireNonNull(TtlRunnable.get(myThread1)));
Thread.sleep(100L);
threadLocal.set("def");
MyThread myThread2 = new MyThread();
executorService.execute(Objects.requireNonNull(TtlRunnable.get(myThread2)));
}
static class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
}
}
}
输出结果 ,没有任何问题!!!
总结
说明TransmittableThreadLocal 支持主线程与线程池(子线程)数据共享。
实现原理(简介)
其实就是TtlRunnable 类,它实现了 Runable 方法,并且重写了 run 方法;在run方法之前复制了父线程的ThreadLocal变量。当线程执行时,调用 TtlRunnable run 方法,TtlRunnable 会从 AtomicReference 中获取出调用线程中所有的上下文,并把上下文给 TransmittableThreadLocal.Transmitter.replay 方法把上下文复制到当前线程。并把上下文备份。当线程执行完,调用 TransmittableThreadLocal.Transmitter.restore 并把备份的上下文传入,恢复备份的上下文,把后面新增的上下文删除,并重新把上下文复制到当前线程。
TransmittableThreadLocal源码地址
https://github.com/alibaba/transmittable-thread-local