本文主要内容
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);
这次在线程池中,可以正常访问外部线程中的数据了


















暂无评论内容