ThreadLocal值传递问题

  • Post author:
  • Post category:其他




ThreadLocal

ThreadLocal是Thread的局部变量,可以理解为ThreadLocalVariable。它在ThreadLocal类中定义了一个ThreadLocalMap,每一个Thread中都有一个该类型的变量——threadLocals——用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。



ThreadLocal在不同线程之间值传递

ThreadLocal与当前线程绑定的变量赋值时,那切换到其他线程则获取不到该值,那么下面看看有没有什么解决办法。

现象 解决方案 描述
同一线程之间传递值 ThreadLocal 给线程提供一个本地变量,当线程消失的时候,所有的本地示例都会被回收
父子线程之间传递值 InheritableThreadLocal jdk提供的类,是ThreadLocal的升级版,解决父子线程之间传递值的问题
线程池不同线程传递值 TransmittableThreadLocal 简称TTL,是阿里巴巴团队提供的一个框架,主要解决因为存储线程池InheritableThreadLocal失效的问题



ThreadLocal示例



示例一 ThreadLocal线程隔离

ThreadLocal是线程的本地变量,下面一个示例是启了十个线程,每个线程都有一个ThreadLocal,每个线程里初始化值都是100,然后每个线程中去改变变量值+1,最后结果可以看出每个线程都是互不影响的。

public class ThreadLocalMapDemo extends Thread {
    /**
     * ThreadLocal特性:
     * 1、线程并发:在多线程并发场景下使用。
     * 2、传递数据:可以通过ThreadLocal在同一线程,不同组件中传递公共变量。
     * 3、线程隔离:每个线程变量都是独立的,不会相互影响。
     *
     * ThreadLocalMap是ThreadLocal的静态内部类
     * 与HashMap类似,初始容量默认是16,初始容量必须是2的整数幂。通过Entry类的数据table存放数据。size是存放的数量,threshold是扩容阈值。
     * Entry继承自WeakReference,key是弱引用,其目的是将ThreadLocal对象的生命周期和线程生命周期解绑。
     * 弱引用:垃圾回收器一旦发现了弱引用的对象,不管内存是否足够,都会回收它的内存。
     */
    private static final ThreadLocal<Integer> THREAD_LOCAL = new ThreadLocal<>();
    private Integer value;

    ThreadLocalMapDemo(Integer value) {
        this.value = value;
    }

    @Override
    public void run() {
        // 设置值不会影响其他线程
        THREAD_LOCAL.set(value);
        System.out.println("Thread name = " + currentThread().getName() + " current value = " + THREAD_LOCAL.get());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 重新设置值
        THREAD_LOCAL.set(THREAD_LOCAL.get() + 1);
        System.out.println("Thread name = " + currentThread().getName() + " current value after change is = " + THREAD_LOCAL.get());
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            ThreadLocalMapDemo demo = new ThreadLocalMapDemo(100);
            demo.setName("线程" + i);
            demo.start();
        }
    }
}



示例二

首先,定义一个ThreadLocal工具类,

/**
 * ThreadLocal工具类
 *
 * @param <T> 范型T
 */
public final class ThreadLocalUtil<T> {
    private final ThreadLocal<T> THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 无参构造函数
     */
    public ThreadLocalUtil() {
    }

    /**
     * 有参构造函数
     *
     * @param t
     */
    ThreadLocalUtil(T t) {
        THREAD_LOCAL.set(t);
    }

    /**
     * 从ThreadLocalMap中获取当前线程变量
     *
     * @return 变量值
     */
    public T getValue() {
        return THREAD_LOCAL.get();
    }

    /**
     * ThreadLocalMap中写入当前线程变量
     *
     * @param t 入参
     * @return 当前写入值
     */
    public T setValue(T t) {
        THREAD_LOCAL.set(t);
        return t;
    }

    /**
     * 从ThreadLocalMap中删除当前线程变量
     */
    public void removeValue() {
        THREAD_LOCAL.remove();
    }
}

主线程中定义一个静态变量util,初始值是123,在子线程和主线程中分别打印ThreadLocal变量值。

public class ThreadLocalExample {
    private static Logger logger = LoggerFactory.getLogger(ThreadLocalExample.class);
    private static ThreadLocalUtil<Integer> util = new ThreadLocalUtil<>(123);

    public static void main(String[] args) {
        new Thread(() -> logger.info("Thread name = {}, thread variable is {}", Thread.currentThread().getName(), util.getValue())).start();
        logger.info("Thread name = {}, thread variable is {}", Thread.currentThread().getName(), util.getValue());
    }
}

示例结果如下,子线程输出的是子线程初始值null,主线程输出的是初始值123。另外,这里

不保证线程的有序性

,只是简易示例。

在这里插入图片描述



InheritableThreadLocal(ITL)示例

InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量时使用。

把上面的代码稍微改造一下,

/**
 * ThreadLocal工具类
 *
 * @param <T> 范型T
 */
public final class ThreadLocalUtil<T> {
    private final ThreadLocal<T> THREAD_LOCAL = new InheritableThreadLocal<>();

    /**
     * 无参构造函数
     */
    public ThreadLocalUtil() {
    }

    /**
     * 有参构造函数
     *
     * @param t
     */
    ThreadLocalUtil(T t) {
        THREAD_LOCAL.set(t);
    }

    /**
     * 从ThreadLocalMap中获取当前线程变量
     *
     * @return 变量值
     */
    public T getValue() {
        return THREAD_LOCAL.get();
    }

    /**
     * ThreadLocalMap中写入当前线程变量
     *
     * @param t 入参
     * @return 当前写入值
     */
    public T setValue(T t) {
        THREAD_LOCAL.set(t);
        return t;
    }

    /**
     * 从ThreadLocalMap中删除当前线程变量
     */
    public void removeValue() {
        THREAD_LOCAL.remove();
    }
}

代码不变,子线程中输出的ThreadLocal变量继承了主线程中的ThreadLocal变量值,

在这里插入图片描述



TransmittableThreadLocal(TTL)示例

TransmittableThreadLocal,简称TTL,它不同于ITL,它能实现池化线程间传递。直接看官方的时序图,

在这里插入图片描述

大部分过程都依赖于 TransmittableThreadLocal 或 TransmittableThreadLocal 中声明的静态工具类 Transmitter 。Transmitter 主要负责 ThreadLocal 的管理和值的传递。

使用前需要导入依赖,

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
            <version>2.12.6</version>
        </dependency>

下面启了两个线程main_01和main_02作为父线程,线程池中执行的三个子线程拿到父线程设置的ttl变量,父线程再修改ttl值,然后线程池中的子线程继续读取ttl中的值。

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TransmittableThreadLocalDemo {
    /**
     * 需要注意的是,使用TTL的时候,要想传递的值不出问题,线程池必须得用TTL加一层代理
     */
    private static ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
    private static ThreadLocal<Integer> ttl = new TransmittableThreadLocal<>();

    public static void main(String[] args) {
        new Thread(() -> {
            String mainThreadName = "main_01";
            ttl.set(1);
            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之前(1), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之前(1), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之前(1), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            sleep(1L); //确保上面的会在tl.set执行之前执行
            ttl.set(2); // 等上面的线程池第一次启用完了,父线程再给自己赋值

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之后(2), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之后(2), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之后(2), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            System.out.println(String.format("线程名称-%s, 变量值=%s", Thread.currentThread().getName(), ttl.get()));

        }).start();


        new Thread(() -> {

            String mainThreadName = "main_02";

            ttl.set(3);

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之前(3), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之前(3), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之前(3), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            sleep(1L); //确保上面的会在tl.set执行之前执行
            ttl.set(4); // 等上面的线程池第一次启用完了,父线程再给自己赋值

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之后(4), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之后(4), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之后(4), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            System.out.println(String.format("线程名称-%s, 变量值=%s", Thread.currentThread().getName(), ttl.get()));

        }).start();

    }

    private static void sleep(long longTime) {
        try {
            Thread.sleep(longTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

最终结果如下,线程池中子线程拿到的是父线程中设置的值。

在这里插入图片描述



总结

ThreadLocal 的使用,本身类似于全局变量,而且是可修改的。一旦中间过程被修改,就无法保证整体流程的前后一致性。

应该尽量避免在业务代码中使用的。


Do not use, only when you know why.

参考链接:

1、https://github.com/alibaba/transmittable-thread-local

2、https://zhuanlan.zhihu.com/p/146124826



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