介绍:
- ThreadLocal:在当前线程中共享数据的,JUC 中提供的
- InheritableThreadLocal:也是JUC中的一个工具类,解决 ThreadLocal 难以解决的问题
- TransmittableThreadLocal:阿里开源的一个工具类,解决上面2个ThreadLocal 难以搞定的问题
第一种情况:ThreadLocal 可以在当前线程中共享数据
用法
在当前线程中,调用 ThreadLocal.set()可以向当前线程中存入数据,然后在当前线程的其他位置可以调用 ThreadLocal.get() 获取当刚才放入的数据。
要点: ThreadLocal.set() 和 ThreadLocal.get() 需要再同一个线程中执行。
案例代码
/*** ThreadLocal的简单存储使用--可以在当前线程中存储数据*/private final ThreadLocal<String> nameThreadLocal1 = new ThreadLocal<>();@Testpublic void threadLocalTest1(){//设置名字nameThreadLocal1.set("张三");//打印获取log.info("主线程中name:{}",nameThreadLocal1.get());//放在子线程中--设置名字为李四new Thread(()->{//继续设置名字nameThreadLocal1.set("李四");log.info("子线程1中name:{}",nameThreadLocal1.get());}).start();//放在子线程中-设置名字王五new Thread(()->{//继续设置名字nameThreadLocal1.set("王五");log.info("子线程2中name:{}",nameThreadLocal1.get());}).start();}
运行输出
16:00:06.258 [main] INFO com.study.tl.ThreadLocalTest - 主线程中name:张三
16:00:06.263 [Thread-1] INFO com.study.tl.ThreadLocalTest - 子线程1中name:李四
16:00:06.263 [Thread-2] INFO com.study.tl.ThreadLocalTest - 子线程2中name:王五
- 主线程(线程名称:main)中放入了张三,取出来也是张三
- 线程 thread1 中放入了李四,取出来也是李四
- 线程 thread2 中放入了王五,取出来也是王五
结论
通过ThreadLocal可以在当前线程中共享数据,通过其set方法在当前线程中设置值,然后在当前线程的其他任何位置,都可以通过ThreadLocal的get方法获取到这个值。
第二种情况:子线程是否可以获取ThreadLocal中的值呢?
代码
/*** 子线程获取不到主线程中ThreadLocal中的值*/private final ThreadLocal<String> nameThreadLocal2 = new ThreadLocal<>();@Testpublic void threadLocalTest2(){//设置名字nameThreadLocal2.set("张三");//打印获取log.info("主线程中name:{}",nameThreadLocal2.get());new Thread(()->{//直接拿主线程设置的值张三log.info("子线程中name:{}",nameThreadLocal2.get());}).start();}
执行输出
16:00:54.617 [main] INFO com.study.tl.ThreadLocalTest - 主线程中name:张三
16:00:54.621 [Thread-1] INFO com.study.tl.ThreadLocalTest - 子线程中name:null
子线程中没有拿到父线程中放进去的"张三",说明ThreadLocal只能在当前线程中共享数据。
结论
子线程无法获取父线程ThreadLocal中的set数据。
通过上面2个案例,可知ThreadLocal生效的条件是:其set和get方法必须在同一个线程才能共享数据。
那么有没有方法解决这个问题呢?(父线程中set数据,子线程中可以get到这个数据的)
JUC中的工具类 InheritableThreadLocal 可以解决这个问题。
第三种情况:InheritableThreadLocal(子线程可以获取父线程中存放的数据)
代码
/*** 使用InheritableThreadLocal解决线程间数据传递问题*/private final ThreadLocal<String> nameThreadLocal3 = new InheritableThreadLocal<>();@Testpublic void threadLocalTest3(){//设置名字nameThreadLocal3.set("张三");//打印获取log.info("主线程中name:{}",nameThreadLocal3.get());new Thread(()->{//直接拿主线程设置的值张三log.info("子线程中name:{}",nameThreadLocal3.get());}).start();}
执行输出
16:03:24.828 [main] INFO com.study.tl.ThreadLocalTest - 主线程中name:张三
16:03:24.832 [Thread-1] INFO com.study.tl.ThreadLocalTest - 子线程中name:张三
结论
使用 InheritableThreadLocal ,子线程可以访问到父线程中通过InheritableThreadLocal.set进去的值。
第四种情况:InheritableThreadLocal:遇到线程池,可能出现问题?
代码
/*** InheritableThreadLocal:遇到线程池 出现问题*/private final ThreadLocal<String> nameThreadLocal4 = new InheritableThreadLocal<>();@Testpublic void threadLocalTest4() throws InterruptedException {//为了看到效果,这里创建大小为1的线程池,注意这里为1才能方便看到效果ExecutorService executorService = Executors.newFixedThreadPool(1);nameThreadLocal4.set("张三");//打印获取log.info("主线程中name设置成张三:{}",nameThreadLocal4.get());executorService.execute(()->{log.info("主线程中name设置成张三,线程池中name值:{}",nameThreadLocal4.get());});//等待执行结束TimeUnit.SECONDS.sleep(1);//再次设置主线程中的值nameThreadLocal4.set("李四");//打印获取log.info("主线程中name设置成李四:{}",nameThreadLocal4.get());executorService.execute(()->{log.info("主线程中name设置成李四,线程池中name值:{}",nameThreadLocal4.get());});}
执行输出
16:08:22.578 [main] INFO com.study.tl.ThreadLocalTest - 主线程中name设置成张三:张三
16:08:22.582 [pool-1-thread-1] INFO com.study.tl.ThreadLocalTest - 主线程中name设置成张三,线程池中name值:张三
16:08:23.593 [main] INFO com.study.tl.ThreadLocalTest - 主线程中name设置成李四:李四
16:08:23.594 [pool-1-thread-1] INFO com.study.tl.ThreadLocalTest - 主线程中name设置成李四,线程池中name值:张三
存在的问题
InheritableThreadLocal 用在线程池上,会有问题,可能导致严重事故,这个一定要知道。
如何解决这个问题呢?
阿里的:TransmittableThreadLocal,这个就是为解决这个问题而来的。
第五种情况:TransmittableThreadLocal:解决线程池中不能够访问外部线程数据的问题
使用方法
需要引入maven配置
<dependency><groupId>com.alibaba</groupId><artifactId>transmittable-thread-local</artifactId><version>2.14.3</version></dependency>
使用 TransmittableThreadLocal 代替 InheritableThreadLocal 和 ThreadLocal
线程池需要用 TtlExecutors.getTtlExecutorService 包裹一下
ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
示例代码
/*** InheritableThreadLocal:遇到线程池 出现问题使用TransmittableThreadLocal解决*/private final ThreadLocal<String> nameThreadLocal5 = new TransmittableThreadLocal<>();@Testpublic void threadLocalTest5() throws InterruptedException {//线程池需要用 TtlExecutors.getTtlExecutorService 包裹一下ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));nameThreadLocal5.set("张三");//打印获取log.info("主线程中name设置成张三:{}",nameThreadLocal5.get());executorService.execute(()->{log.info("主线程中name设置成张三,线程池中name值:{}",nameThreadLocal5.get());});//等待执行结束TimeUnit.SECONDS.sleep(1);//再次设置主线程中的值nameThreadLocal5.set("李四");//打印获取log.info("主线程中name设置成李四:{}",nameThreadLocal5.get());executorService.execute(()->{log.info("主线程中name设置成李四,线程池中name值:{}",nameThreadLocal5.get());});}
执行输出
16:27:00.716 [main] INFO com.study.tl.ThreadLocalTest - 主线程中name设置成张三:张三
16:27:00.727 [pool-1-thread-1] INFO com.study.tl.ThreadLocalTest - 主线程中name设置成张三,线程池中name值:张三
16:27:01.740 [main] INFO com.study.tl.ThreadLocalTest - 主线程中name设置成李四:李四
16:27:01.741 [pool-1-thread-1] INFO com.study.tl.ThreadLocalTest - 主线程中name设置成李四,线程池中name值:李四
。