ThreadLocal、InheritableThreadLocal、TransmittableTheadLocal

本文主要内容

ThreadLocal:在当前线程中共享数据的,JUC中提供的。

InheritableThreadLocal:数据从父线程传递给子线程。(也是JUC中的一个工具类,解决ThreadLocal难以解决的问题)

TransmittableThreadLocal:数据在任意线程间传递(阿里开源的一个工具类,解决上面2个ThreadLocal难以搞定的问题)。

1、ThreadLocal可以在当前线程中共享数据

用法:

在当前线程中,调用ThreadLocal的set()方法可以向当前线程中存入数据,然后在当前线程的其他位置可以调用ThreadLocal.get()获取刚才放入的数据。

要点:ThreadLocal.set()和ThreadLocal.get()需要在同一个线程中执行。

@Test
    public void threadLocalTest1() throws InterruptedException {
        //将用户名放入 userNameTL 中
        userNameTL.set("张三");
 
        //在m1中,取上面放入的张三,看看能不能取到
        m1();
 
        //这里创建了线程 thread1,里面放入了李四,然后在m1中取出用户名,看看是不是李四?
        new Thread(() -> {
            userNameTL.set("李四");
            m1();
        }, "thread1").start();
        TimeUnit.SECONDS.sleep(1);
 
        //这里创建了线程 thread2,里面放入了王五,然后在m1中取出用户名,看看是不是王五
        new Thread(() -> {
            userNameTL.set("王五");
            m1();
        }, "thread2").start();
        TimeUnit.SECONDS.sleep(1);
    }
 
 public void m1() {
        logger.info("userName:{}", userNameTL.get());
    }

运行输出:

17:57:57 [main] m1 - userName:张三
17:57:57 [thread1] m1 - userName:李四
17:57:58 [thread2] m1 - userName:王五

1、主线程中放入了张三,取出来也是张三

2、线程thread1中放入了李四,取出来也是李四

3、线程thread2中放放了王五,取出来也是王五

结论

通过ThreadLocal可以在当前线程中共享数据,通过其set方法在当前线程中设置值,然后在当前线程的其他任何位置,都可以通过ThreadLocal的get方法获取到这个值。

2、子线程是否可以获取ThreadLocal中的值呢

  @Test
    public void threadLocalTest2() throws InterruptedException {
        //这里是主线程,ThreadLocal中设置了值:张三
        userNameTL.set("张三");
        logger.info("userName:{}", userNameTL.get());
 
        //创建了一个子线程thread1,在子线程中去ThreadLocal中拿值,能否拿到刚才放进去的“张三”呢?
        new Thread(() -> {
            logger.info("userName:{}", userNameTL.get());
        }, "thread1").start();
 
        TimeUnit.SECONDS.sleep(1);
    }

运行输出 :

18:01:56 [main] threadLocalTest2 - userName:张三
18:01:56 [thread1] lambda$threadLocalTest2$2 - userName:null

子线程中没有拿到父线程中放进去的“张三”,说明ThreadLocal只能在当前线程中共享数据

结论:

子线程无法获取父线程ThreadLocal中set的数据

ThreadLocal只有在同一个线程才能共享数据,要解决父线程中set数据,子线程中可以get到这个数据,使用JUC工具类InheritableThreadLocal。

3、InheritableThreadLocal(子线程可以获取父线程中存放的数据)

 private InheritableThreadLocal<String> userNameItl = new InheritableThreadLocal<>();
 
    @Test
    public void inheritableThreadLocal1() throws InterruptedException {
        //这里是主线程,使用 InheritableThreadLocal.set 放入值:张三
        userNameItl.set("张三");
        logger.info("userName:{}", userNameItl.get());
 
        //创建了一个子线程thread1,在子线程中去ThreadLocal中拿值,能否拿到刚才放进去的“张三”呢?
        new Thread(() -> {
            logger.info("userName:{}", userNameItl.get());
        }, "thread1").start();
        TimeUnit.SECONDS.sleep(1);
    }

运行输出:

18:08:18 [main] inheritableThreadLocal1 - userName:张三
18:08:18 [thread1] lambda$inheritableThreadLocal1$3 - userName:张三

结论:

使用InheritableThreadLocal,子线程可以访问父线程中通过
InheritableThreadLocal.set进去的值。

4、InheritableThreadLocal:遇到线程池会怎么样呢?

    @Test
    public void inheritableThreadLocal2() throws InterruptedException {
        //为了看到效果,这里创建大小为1的线程池,注意这里为1才能方便看到效果
        ExecutorService executorService = Executors.newFixedThreadPool(1);
 
        //主线程中,放入了张三
        userNameItl.set("张三");
        logger.info("userName:{}", userNameItl.get());
 
        //在线程池中通过 InheritableThreadLocal 拿值,看看能否拿到 刚才放入的张三?
        executorService.execute(() -> {
            logger.info("第1次获取 userName:{}", userNameItl.get());
        });
 
        //这里稍微休眠一下,等待上面的任务结束
        TimeUnit.SECONDS.sleep(1);
 
        //这里又在主线程中放入了李四
        userNameItl.set("李四");
        logger.info("userName:{}", userNameItl.get());
 
        //这里又在线程池中通过 InheritableThreadLocal.get 方法拿值,看看能否拿到 刚才放入的李四?
        executorService.execute(() -> {
            //在线程池中通过 inheritableThreadLocal 拿值,看看能否拿到?
            logger.info("第2次获取 userName:{}", userNameItl.get());
        });
 
        TimeUnit.SECONDS.sleep(1);
    }

运行输出:

18:10:32 [main] inheritableThreadLocal2 - userName:张三
18:10:32 [pool-1-thread-1] lambda$inheritableThreadLocal2$4 - 第1次获取 userName:张三
18:10:33 [main] inheritableThreadLocal2 - userName:李四
18:10:33 [pool-1-thread-1] lambda$inheritableThreadLocal2$5 - 第2次获取 userName:张三

结果:

从结果中看,线程池执行了2次,2次拿的都是张三,和主线程第一次放入的值是一样的,而第二次主线程放入的是李四,但是第二次线程池中拿到的却是张三。

分析下缘由:

上面线程池的大小是1,也就是说这个线程池中只有一个线程,所以让线程池执行的2次任务用到的都是一个线程,从上面的日志中可以看到线程名称都是pool-1-thread-1,说明这两次任务都是线程池中同一个线程执行的。

线程池中的线程是重复利用的,线程池中的这个线程是什么时候创建的?谁创建的?它的父线程是谁呢?

是主线程中第一次调用executorService.execute让线程池执行任务的时候,线程池发现当前线程数少于核心线程数,所以会创建一个线程。

他的父线程是创建他的线程,也就是执行executor.execute的线程,即主线程。

子线程创建的时候,子线程会将父线程中InheritableThreadLocal的值复制一份到子线程的InheritableThreadLocal中。父线程中的InheritableThreadLocal第一次设置的张三,之后就调用线程池的executor方法执行任务,此时,会创建子线程,子线程会将父线程的InheritableThreadLocal设置的张三复制到子线程的InheritableThreadLocal中,此时子线程池的值就是从父线程中复制过来的。

复制之后,父子线程的InheritableThreadLocal就没有关系了,父线程的InheritableThreadLocal的值再修改,也不会影响子线程中的值了,所以子线程两次输出的都是张三。

如何解决这个问题?TransmittableThreadLocal,就是解决这个问题而来的。

5、TransmittableThreadLocal:解决线程池中不能够访问外部线程数据的问题

引入maven配置:

 <!-- 线程传递值 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
            <version>2.14.3</version>
        </dependency>

使用TransmittableThreadLocal取代InheritableThreadLocal和ThreadLocal

代码示例:

    TransmittableThreadLocal<String> userNameTtl = new TransmittableThreadLocal<String>();
 
    @Test
    public void transmittableThreadLocal1() throws InterruptedException {
        //为了看到效果,这里创建大小为1的线程池,注意这里为1才能方便看到效果
        ExecutorService executorService = Executors.newFixedThreadPool(1);
 
        //这里需要用 TtlExecutors.getTtlExecutorService 将原线程池包装下
        executorService = TtlExecutors.getTtlExecutorService(executorService);
 
        // 主线程中设置 张三
        userNameTtl.set("张三");
        logger.info("userName:{}", userNameTtl.get());
 
        //在线程池中通过 TransmittableThreadLocal 拿值,看看能否拿到 刚才放入的张三?
        executorService.execute(() -> {
            logger.info("第1次获取 userName:{}", userNameTtl.get());
        });
        TimeUnit.SECONDS.sleep(1);
 
        //这里放入了李四
        userNameTtl.set("李四");
        logger.info("userName:{}", userNameTtl.get());
 
        //在线程池中通过 TransmittableThreadLocal 拿值,看看能否拿到 刚才放入的李四?
        executorService.execute(() -> {
            //在线程池中通过 inheritableThreadLocal 拿值,看看能否拿到?
            logger.info("第2次获取 userName:{}", userNameTtl.get());
        });
 
        TimeUnit.SECONDS.sleep(1);
    }

注:线程池需要用
TtlExecutors.getTtlExecutorService包裹一下:

 executorService = TtlExecutors.getTtlExecutorService(executorService);

这次在线程池中,可以正常访问外部线程中的数据了

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
茶饮观察员的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容