加强版的ThreadLocal-TransmittableThreadLocal

1、前言

ThreadLocal是JDK里面提供的一个thread-local(线程局部)的变量,当一个变量被声明为ThreadLocal时候,每一个线程会持有该变量的一个独有副本;可是ThreadLocal不支持继承性,虽然JDK里面提供了InheritableThreadLocal来解决继承性问题,可是其也是不完全的,本节咱们谈谈加强的TransmittableThreadLocal,其能够很好解决线程池状况下继承问题。git

2、TransmittableThreadLocal

前面说了,当一个变量被声明为ThreadLocal时候,每一个线程会持有该变量的一个独有副本,好比下面例子:github

private static ThreadLocal<String> parent = new ThreadLocal<String>();

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            try {
                // 设置本线程变量
                parent.set(Thread.currentThread().getName() + "hello,jiaduo");

                // dosomething
                Thread.sleep(3000);

                // 使用线程变量
                System.out.println(Thread.currentThread().getName() + ":" + parent.get());

                // 清除
                parent.remove();
                
                // do other thing
                //.....

            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "thread-1").start();

        new Thread(() -> {
            try {
                // 设置本线程变量
                parent.set(Thread.currentThread().getName() + "hello,jiaduo");

                // dosomething
                Thread.sleep(3000);

                // 使用线程变量
                System.out.println(Thread.currentThread().getName() + ":" + parent.get());

                // 清除
                parent.remove();
                
                // do other thing
                //.....
                
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "thread-2").start();
}复制代码

如上代码线程1和线程2各自持有parent变量中的副本,其相互之间并发访问本身的副本变量,不会存在线程安全问题。编程

可是ThreadLocal不支持继承性:安全

public static void main(String[] args) throws InterruptedException {

        ThreadLocal<String> parent = new ThreadLocal<String>();
        parent.set(Thread.currentThread().getName() + "hello,jiaduo");

        new Thread(() -> {

            try {
                
                // 使用线程变量
                System.out.println(Thread.currentThread().getName() + ":" + parent.get());

                // do other thing
                // .....

            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "child-thread").start();
}复制代码

如上代码main线程内设置了线程变量,而后在main线程内开启了子线程child-thread,而后在子线程内访问了线程变量,运行会输出:child-thread:null;也就是子线程访问不了父线程设置的线程变量;JDK中InheritableThreadLocal能够解决这个问题:并发

InheritableThreadLocal<String> parent = new InheritableThreadLocal<String>();
        parent.set(Thread.currentThread().getName() + "hello,jiaduo");

        new Thread(() -> {

            try {
                
                // 使用线程变量
                System.out.println(Thread.currentThread().getName() + ":" + parent.get());

                // do other thing
                // .....

            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "child-thread").start();复制代码

运行代码会输出:child-thread:mainhello,jiaduo,可知InheritableThreadLocal支持继承性。可是InheritableThreadLocal的继承性是在new Thread建立子线程时候在构造函数内把父线程内线程变量拷贝到子线程内部的(能够参考《Java并发编程之美》一书),而线上环境咱们不多亲自new线程,而是使用线程池来达到线程复用,线上环境通常是把异步任务投递到线程池内执行;因此父线程向线程池内投递任务时候,可能线程池内线程已经建立完毕了,因此InheritableThreadLocal就起不到做用了,例以下面例子:异步

// 0.建立线程池
    private static final ThreadPoolExecutor bizPoolExecutor = new ThreadPoolExecutor(2, 2, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>(1));

    public static void main(String[] args) throws InterruptedException {

        // 1 建立线程变量
        InheritableThreadLocal<String> parent = new InheritableThreadLocal<String>();

        // 2 投递三个任务
        for (int i = 0; i < 3; ++i) {
            bizPoolExecutor.execute(() -> {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            });

        }

        // 3休眠4s
        Thread.sleep(4000);

        // 4.设置线程变量
        parent.set("value-set-in-parent");

        // 5. 提交任务到线程池
        bizPoolExecutor.execute(() -> {
            try {
                // 5.1访问线程变量
                System.out.println("parent:" + parent.get());
            } catch (Exception e) {
                e.printStackTrace();
            }

        });
}复制代码

如上代码2向线程池投递3任务,这时候线程池内2个核心线程会被建立,而且队列里面有1个元素。而后代码3休眠4s,旨在让线程池避免饱和执行拒绝策略,而后代码4设置线程变量,代码5提交任务到线程池。运行输出:parent:null,可知子线程内访问不到父线程设置变量。函数

下面咱们使用TransmittableThreadLocal修改代码以下:spa

public static void main(String[] args) throws InterruptedException {

        // 1 建立线程变量
        TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();

        // 2 投递三个任务
        for (int i = 0; i < 3; ++i) {
            bizPoolExecutor.execute(() -> {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            });

        }

        // 3休眠4s
        Thread.sleep(4000);

        // 4.设置线程变量
        parent.set("value-set-in-parent");

        // 5. 提交任务到线程池
        Runnable task = () -> {
            try {
                // 5.1访问线程变量
                System.out.println("parent:" + parent.get());
            } catch (Exception e) {
                e.printStackTrace();
            }

        };
        
        // 额外的处理,生成修饰了的对象ttlRunnable
        Runnable ttlRunnable = TtlRunnable.get(task);
        
        bizPoolExecutor.execute(ttlRunnable);
}复制代码

如上代码5咱们把具体任务使用TtlRunnable.get(task)包装了下,而后在提交到线程池,运行代码,输出:parent:value-set-in-parent,可知子线程访问到了父线程的线程变量线程

3、总结

TransmittableThreadLocal完美解决了线程变量继承问题,其是淘宝技术部 哲良开源的一个库,github地址为:github.com/alibaba/tra…,后面咱们会探讨其内部实现原理,敬请期待。code

file

相关文章
相关标签/搜索