ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal 使用以及区别

前言

本文主要是用实际案例比较下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


版权声明:本文为qq_39381772原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。