当前位置:   article > 正文

线程上下文ThreadLocal_通过线程获取threadlocal

通过线程获取threadlocal

ThreadLocal

作用?在同一个线程内,可以传递数据。

什么意思呢?就是一个请求进来的时候,系统内是要经过很多个类和很多个方法的,那怎么在多个类和多个方法之间传递数据呢?靠ThreadLocal。

说白了,就是创建一个对象:

  1. ThreadLocal threadLocal = new ThreadLocal();
  2. 复制代码

然后这个对象threadLocal<线程对象,数据>,存储了所有请求线程的数据。也就是说,你在不同地方读数据的时候,只要是同一个线程,那么读出来的数据就是同一个数据。这样就实现了在同一个线程内部的不同处理环节传递数据的作用。

说白了,就是map<线程对象,数据>。只不过键值对特殊一点,key是某个线程对象,value是数据。


如果同一个线程内部,要传递多个数据咋办?

创建多个threadLocal对象。

说白了,就是每个threadLocal对象,如果是同一个线程,就只能传递一个数据。也就是说,threadLocal对象存储的是不同线程的数据,但是呢,同一个线程只能传递一个数据。

如果同一个线程,要传递多个数据,解决方法就是创建多个threadLocal对象。


那threadLocal对象本身是怎么被传递的呢?

其实也不是传递,其实就是怎么才能在不同的地方都能访问到?

基于工具类 + 静态数据:

  1. 工具类{
  2. private static ThreadLocal threadLocal = new ThreadLocal();
  3. //写数据
  4. set(){
  5. threadLocal.set(数据); //写数据
  6. }
  7. //读数据
  8. get(){
  9. threadLocal.get(); //读数据
  10. }
  11. }
  12. 复制代码

即在不同的地方要访问一个数据,就使用工具类 + 静态数据。


缺点?缺点是什么呢?不能把父线程的数据传递给子数据。

举个例子,父线程写了数据,然后子线程去读,读不到,即读出来为null,因为本来子线程就没有数据,所以肯定是null。

看代码

  1. /**
  2. * @author javaself
  3. */
  4. public class InheritableThreadLocalTest2 {
  5. public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
  6. public static ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
  7. public static void main(String[] args) {
  8. threadLocal.set("threadLocal 的值: hello");
  9. inheritableThreadLocal.set("threadLocal 的值: hello inheritableThreadLocal");
  10. new Thread(() -> {
  11. System.out.println("子线程获取到的值是:");
  12. //读不到父线程的数据
  13. System.out.println(threadLocal.get()); //null
  14. //可以读到父线程的数据
  15. System.out.println(inheritableThreadLocal.get()); //threadLocal 的值: hello inheritableThreadLocal
  16. }).start();
  17. }
  18. }
  19. 复制代码

那怎么解决这个问题呢?只能才能把父线程的数据传递给子线程呢?这个时候就要用到jdk自带的InheritableThreadLocal类。类的名字,就是可传递的意思。

父子线程传递数据-InheritableThreadLocal

还是刚才的代码,看截图

这个是测试结果,InheritableThreadLocal是可以把父线程的数据传递给子线程的。

那实现原理是什么呢?

实现原理

核心原理有两点

1、  InheritableThreadLocal用了一个单独的map

2、  InheritableThreadLocal重写了getMap方法

接下来,讲细节。


首先,InheritableThreadLocal用了一个单独的map。作用是什么呢?也是存储数据,也是<线程对象,数据>,其实和上文最开始提到的map基本上完全一样,只不过这个map是一个单独的map对象,而且是专门用来解决传递数据的问题的。

来看下Thread源码:

  1. /* ThreadLocal values pertaining to this thread. This map is maintained
  2. * by the ThreadLocal class. */
  3. ThreadLocal.ThreadLocalMap threadLocals = null;
  4. /*
  5. * InheritableThreadLocal values pertaining to this thread. This map is
  6. * maintained by the InheritableThreadLocal class.
  7. */
  8. ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; //这个map专门用来解决传递数据的问题
  9. 复制代码

说白了,就是任何一个Thread线程对象都包含了两个map,一个是不能传递数据,一个是专门传递的。就看你写数据的时候,是写到哪个map?如果写到不能传递数据的map,子线程读的时候也就不能传递;如果写到传递数据的map,子线程就可以读父线程的数据。


刚才最后这段话,其实就已经说出了重点,即写和读的时候,到底使用的是哪个map?

那这个问题,其实就是我们核心原理的第二点,即:InheritableThreadLocal重写了getMap方法。

具体是怎么重写的呢?看InheritableThreadLocal源码:

  1. public class InheritableThreadLocal<T> extends ThreadLocal<T> {
  2. /**
  3. * Get the map associated with a ThreadLocal.
  4. *
  5. * @param t the current thread
  6. */
  7. ThreadLocalMap getMap(Thread t) {
  8. return t.inheritableThreadLocals; //返回的是可传递数据的map
  9. }
  10. 复制代码

Thread类的源码,包含了两个map。然后,InheritableThreadLocal类重写了getMap方法,返回数据就是可重写的map。

为什么重写getMap方法,就可以实现传递功能呢?

因为InheritableThreadLocal继承了ThreadLocal。

在写数据的时候,其实就是调用ThreadLocal的set方法,set方法里面会调用getMap方法,由于InheritableThreadLocal继承并且重写了ThreadLocal的getMap方法,所以调用的时候,就调用了InheritableThreadLocal的getMap方法,这个时候获取到的就是可传递map。

来看下ThreadLocal源码,set方法:

  1. /**
  2. * Sets the current thread's copy of this thread-local variable
  3. * to the specified value. Most subclasses will have no need to
  4. * override this method, relying solely on the {@link #initialValue}
  5. * method to set the values of thread-locals.
  6. *
  7. * @param value the value to be stored in the current thread's copy of
  8. * this thread-local.
  9. */
  10. public void set(T value) {
  11. Thread t = Thread.currentThread();
  12. ThreadLocalMap map = getMap(t); //调用了父类InheritableThreadLocal重写的getMap方法
  13. if (map != null)
  14. map.set(this, value);
  15. else
  16. createMap(t, value);
  17. }
  18. 复制代码

get方法,同理。


刚才讲的都是理论和源码,我们来实战调试一下源码,就知道为什么子线程可以读父线程的数据了。

测试代码:

  1. public class InheritableThreadLocalTest4 {
  2. public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
  3. public static ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
  4. public static void main(String[] args) {
  5. inheritableThreadLocal.set("threadLocal 的值: hello inheritableThreadLocal"); //写数据到main线程的可传递map
  6. new Thread(() -> {
  7. //可以读到父线程的数据
  8. System.out.println(inheritableThreadLocal.get()); //threadLocal 的值: hello inheritableThreadLocal //子线程读的时候,也是从main线程的可传递map读数据
  9. }).start();
  10. }
  11. }
  12. 复制代码

先看写数据即set方法源码,下面的截图有两个关键点:第一个是父线程就是main线程,第二个是写数据到父线程的可传递map<main线程对象,数据>。

再来看读数据即get方法源码,下面截图是子线程读数据即调用get方法:

截图说明,重点是getMap方法:只要是同一个InheritableThreadLocal对象,那么getMap方法的返回数据就是同一个数据,即main线程的可传递map里的数据。也就是说,main方法如果创建了多个子线程,那么多个子线程都是从main线程的可传递map里读同一个数据。


上文提到,子线程读数据的时候,getMap方法读到的是父线程的数据,这个说法其实是不准确的,准确的说法是,子线程读到的是子线程的数据,即子线程对象.可传递map。只不过子线程对象.可传递map和main线程对象.可传递的值一样,所以相当于就实现了父线程数据传递到子线程的功能。

说白了,核心点就是,子线程对象.可传递map和父线程对象.可传递map的值一样,但是二者是独立的,只不过值是一样的。

那为什么值会一样呢?因为子线程对象.可传递map就是从父线程对象.可传递map复制过来的。

直接看源码:Thread.init方法

  1. if (inheritThreadLocals && parent.inheritableThreadLocals != null)
  2. this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); //把父线程map复制到子线程map
  3. 复制代码

那Thread.init方法什么时候调用呢?就是在main方法里的new Thread的时候调用的。

  1. public Thread(Runnable target) {
  2. //调用init方法
  3. init(null, target, "Thread-" + nextThreadNum(), 0);
  4. }
  5. 复制代码

到此为此,基本上把实现原理和源码分析讲清楚了。


缺点?InheritableThreadLocal有什么缺点呢?

线程池不能传递数据,即不能把父线程数据传递给子线程,为什么呢?因为线程池的线程对象是复用的,那为什么重复使用线程对象就不能传递呢?

准确的说,线程池不是不能传递,其实也能传递,而且实现原理和源码分析也完全一样。但是,如果main线程后面修改了数据,子线程读的数据仍然是旧数据。为什么呢?因为子线程只在创建线程的时候才会把父线程的数据复制到子线程,如果父线程后面又修改了数据,那么同一个子线程读的仍然是旧数据。

测试代码:

  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. import java.util.concurrent.TimeUnit;
  4. /**
  5. * @author javaself
  6. */
  7. public class InheritableThreadLocalTest5 {
  8. static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
  9. public static void main(String[] args) throws InterruptedException {
  10. ExecutorService executorService = Executors.newFixedThreadPool(1);
  11. inheritableThreadLocal.set("i am a parent");
  12. executorService.execute(new Runnable() {
  13. @Override
  14. public void run() {
  15. System.out.println(inheritableThreadLocal.get()); //i am a parent //旧数据
  16. }
  17. });
  18. TimeUnit.SECONDS.sleep(1);
  19. inheritableThreadLocal.set("i am a new parent");// 设置新的值
  20. executorService.execute(new Runnable() {
  21. @Override
  22. public void run() {
  23. System.out.println(inheritableThreadLocal.get()); //i am a parent //仍然是旧数据
  24. }
  25. });
  26. }
  27. }
  28. 复制代码

可以发现,两次打印结果都是旧数据,即两次读的读是旧数据。为什么呢?原因就是因为子线程始终是同一个子线程,即复用了线程池里的线程对象。

另外,如果是子线程修改了数据,那么重复使用同一个子线程的时候,读的就是子线程刚刚设置的新数据。这个时候为什么又可以读到新数据呢?因为修改的本来就是子线程的数据,所以如果是同一个子线程肯定是可以读到自己最新的数据的。

看下测试代码就知道了:

  1. public static void main(String[] args) throws InterruptedException {
  2. ExecutorService executorService = Executors.newFixedThreadPool(1);
  3. inheritableThreadLocal.set("i am a inherit parent");
  4. executorService.execute(new Runnable() {
  5. @Override
  6. public void run() {
  7. System.out.println(inheritableThreadLocal.get()); //旧数据
  8. inheritableThreadLocal.set("i am a old inherit parent");// 子线程中设置新的值
  9. }
  10. });
  11. TimeUnit.SECONDS.sleep(1);
  12. inheritableThreadLocal.set("i am a new inherit parent");// 主线程设置新的值
  13. executorService.execute(new Runnable() {
  14. @Override
  15. public void run() {
  16. System.out.println(inheritableThreadLocal.get()); //子线程可以读到自己的最新的数据
  17. }
  18. });
  19. }
  20. 打印结果:
  21. i am a inherit parent
  22. i am a old inherit parent
  23. 复制代码

可以看到,如果是子线程自己修改数据,那么当线程池里的子线程被复用的时候,读到的数据是新数据。因为修改数据和读数据,都是在同一个子线程自己的可传递map。


那这个问题咋解决呢?就是怎么才能实现线程池复用子线程的时候,始终可以读到父线程的最新数据呢?阿里ThreadLocal。

线程池传递数据-阿里ThreadLocal

作者:Java个体户
原文链接:https://juejin.cn/post/7185898791470366781
 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/949053
推荐阅读
相关标签
  

闽ICP备14008679号