你知道TheadLocal与InheritableThreadLocal吗

298 阅读4分钟

土味情话:推荐一个0卡又很甜的零食,我的嘴巴

前言

首先想一下ThreadLocal的应用场景是什么呢?

一般来说,ThreadLocal在项目中用来保存用户的信息(例如session等)将当前用户和当前线程绑定,可以在同一个线程中的任何地方获取到该信息。

那么ThreadLocal的缺点是什么呢?

刚才说到ThreadLocal将数据和当前线程绑定,所以呢?没错,如果不是同一个线程,那么无法获取到其他线程的数据。而InheritableThreadLocal就是用来解决这个问题

###ThreadLocal怎么保存数据的?

  • ThreadLocal的set方法

    看看set方法到底是如何保存数据,如何做到线程独享

    public void set(T value) {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取当前线程维护的`ThreadLocalMap`对象(可以认为是map)
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // 直接保存
        		map.set(this, value);
        else
            // 创建`ThreadLocalMap`之后再保存
        		createMap(t, value);
    }
    

    其实,看到这里就已经可以了,每个value是保存在每个线程维护的ThreadLocalMap对象中和每个线程是绑定的,所以看似是调用的同一个ThreadLocal对象来保存数据,其实在并不是将数据保存在ThreadLocal中,而是保存在Thread维护的ThreadLocalMap中,做到的资源隔离。

  • 多线程中无法共享数据

    所以,在A线程中保存的数据,可以在A线程中任意获取。但是如果这个时候新开了一个B线程,B线程中肯定就获取不到A线程中保存的数据。因为是两个不同的线程,在获取数据的时候需要获取到当前线程Thread对象,然后获取当前线程维护的ThreadLocalMap,然后再获取指定的数据。Thread都不是一个,所以当然也获取不到其他线程的数据啦

    看看ThreadLocalget方法就明白了

    public T get() {
      	// 首先获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取当前线程对象维护的`ThreadLocalMap`
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 从`ThreadLocalMap`中获取当前key(threadLocal对象)对应的value
            ThreadLocalMap.Entry e = map.getEntry(this);
        		if (e != null) {
                T result = (T)e.value;
                return result;
        		}
        }
        return setInitialValue();
    }
    

InheritableThreadLocal怎么传递数据的?

InheritableThreadLocal字面意思为:可被继承的ThreadLocal,所以作用也很明显,可以让子线程拥有父线程ThreadLocal中的数据,相当于共享。不过这个共享是一次性的,如果父线程更新了ThreadLocal的值,那么更新的数据不会实现同步

  • 父子线程共享数据示例代码

    private static InheritableThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<>();
    
    @Test
    public void asyncInherriableThreadLocal() {
      	// 在main线程中向InheritableThreadLocal中保存数据
      	inheritableThreadLocal.set(300);
    
      	// 在子线程1中获取InheritableThreadLocal中的数据
      	new Thread(() -> {
             System.out.println("future1---inheritableThreadLocal:" + inheritableThreadLocal.get());
        }).start();
    		
        TimeUnit.SECONDS.sleep(1);
    		
      	System.out.println("main----inheritableThreadLocal:" + inheritableThreadLocal.get());
    }
    

    控制台输出:

    future1---inheritableThreadLocal:300
    main----inheritableThreadLocal:300
    

    main线程中保存的数据可以在线程1中获取到,表示通过InheritableThreadLocal父子线程可以共享数据

  • 如何实现父子线程共享数据

    • 创建线程的过程中发生了什么?

      • 第一步,new Thread()中调用init()
      private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
          // 注意最后一个参数`true`,默认是开启InheritThreadLocal
      		init(g, target, name, stackSize, null, true);
      }
      
      • 第二步,判断是否开启dd,然后执行创建当前线程的ThreadLocalMap
      private void init(ThreadGroup g, Runnable target, String name,
                            long stackSize, AccessControlContext acc,
                            boolean inheritThreadLocals) {
      ...
      
      if (inheritThreadLocals && parent.inheritableThreadLocals != null)
      						// 创建当前线程的`ThreadLocalMap`对象,并通过父线程中维护的`inheritableThreadLocals`该变量即`InheritableThreadLocal`对象
                  this.inheritableThreadLocals =              ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
      
      ...
        
      }
      
      • 第三步,将父线程的ThreadLocalMap中的数据复制一份到子线程的ThreadLocalMap(这也是InheritableThreadLocal可以共享数据的原因)
      private ThreadLocalMap(ThreadLocalMap parentMap) {
        	// 获取到父线程的`ThreadLocalMap`中的Entry数组
          Entry[] parentTable = parentMap.table;
          int len = parentTable.length;
          setThreshold(len);
          table = new Entry[len];
      		
          // 遍历父线程中的`ThreadLocalMap`中的Entry数组
          for (int j = 0; j < len; j++) {
              Entry e = parentTable[j];
              if (e != null) {
                  @SuppressWarnings("unchecked")
                  ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                  if (key != null) {
                      // 获取父线程的值
                      Object value = key.childValue(e.value);
                    	// 创建一个新的Entry对象,用于保存到`ThreadLocalMap`
                      Entry c = new Entry(key, value);
                      int h = key.threadLocalHashCode & (len - 1);
                      while (table[h] != null)
                          h = nextIndex(h, len);
                      // 保存到当前线程的`ThreadLocalMap`维护的`Entry[]`中,实现数据传递
                      table[h] = c;
                      size++;
                  }
          		}
          }
      }
      

    第三步就是InheritableThreadLocal是怎么传递数据从当前线程到子线程,实现数据共享的,也是为什么后续更新数据,子线程不会同步更新数据的原因。

小总结

  • 如何实现数据传递到子线程中?

    当前线程中创建子线程的时候,默认会将当前线程中的ThreadLocalMap进行遍历,将里面的所有数据都复制一份保存到创建的新线程的ThreadLocalMap

  • 为什么不能同步更新?

    因为InheritableThreadLocal实现数据传递并不是使用一个容器,可以复制一份数据到新创建的线程,所以只能同步创建时刻的数据,无法同步更新数据。

微信公众号「指尖上的代码」,欢迎关注~

你的点赞和关注是写文章最大的动力~