ThreadLocal的效果很明显,对同一个变量,各个线程能保存自己的值,使用起来也很简单,就三个方法,get , set , remove 。简单操作的背后,jdk帮我们隐藏了很多逻辑,今天我们一起看看threadLocal的使用的背后的原理

ThreadLocal的使用


使用没准备用多少篇辐来写,因为使用就三个操作,初始化时set,使用时 get , 不用时remove 下面用一段简单的代码演示下

   //初始化两个threadLocal
   static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
   static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
   public static void main(String[] args) {
       //新建两个线程
       Thread t1 = new Thread(()->{
           integerThreadLocal.set(1);
           System.out.println(integerThreadLocal.get());
           stringThreadLocal.set("aaa");
           System.out.println(stringThreadLocal.get());
       });
       Thread t2 = new Thread(()->{
           integerThreadLocal.set(2);
           System.out.println(integerThreadLocal.get());
           stringThreadLocal.set("bbb");
           System.out.println(stringThreadLocal.get());
       });
       //启动看输出结果
       t1.start();t2.start();
   }

结果如下图所示:

这样就达到了同一个变量对不同的线程有不同的返回值的效果,小伙伴们有没有想一想,这个是怎么实现的呢?

ThreadLocal中的set()


最开始看这部分源码的时候,从set方法开始看,部分源码如下所示:

   public void set(T value) {
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       if (map != null) {
           //实际上是使用了map的set
           map.set(this, value);
       } else {
           createMap(t, value);
       }
   }
在这里面我们可以看到,我们使用set时,实际上是使用了ThreadLocalMap的set方法,我们点进去getMap(t) 

   /**
    * @param  t 当前线程
    * @return the map
    */
   ThreadLocalMap getMap(Thread t) {
       return t.threadLocals;
   }

也就是说,这个Map实际上是当前线程里面的 threadLocals 字段。我们跟踪到Thead类中如下图所示,可以看到,这个threadLocals 是null , 在createMap时才创建,通过下面的代码可以看到,对这个threadLocals赋值是new ThreadLocalMap(this,firstValue) 和我们认识的HashMap等map类似,key 是 当前的threadLocal,value是当前的设置的值。

现在大家跟我一起回到第一段代码,在set的时候,如果获取到的map为空,则执行createMap方法。this 就是我们当前的threadLocal实例。根据上述,我们可以简单理一下:

一个Thread里面有一个ThreadLocalMap, 这个ThreadLocalMap里面存放着当前线程用到的ThreadLocal

ThreadLocal中的Get()


大致知道这个结构后,我们可以自己想一下get怎么实现的,可能是以下三步:

  1. 获取到当前线程
    
  2. 获取到当前线程中的ThreadLocalMap,也就是threadLocals变量
    
  3. 传入当前的ThreadLocal实例,获取值
    

get()源码职下所示:


public T get() {
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       if (map != null) {
           ThreadLocalMap.Entry e = map.getEntry(this);
           if (e != null) {
               @SuppressWarnings("unchecked")
               T result = (T)e.value;
               return result;
           }
       }
       return setInitialValue();
   }

一看,果然也就是这几步,和我们想的大体是一致的,只不过比我们更加严谨,又加入了判空等操作。

可能有些小伙伴对传入当前ThreadLocal这块还不太理解,我们对使用篇的示例代码debug看下:小伙伴们可以自己debug下,有助于理解ThreadLocal变量实例的作用

总结


ThreadLocal是多线程这块入门级的知识点,本来对这块也没在意,最近工作用到了这个,就稍稍挖了一下,不挖不知道,一挖吓一跳。而且这还没有很深处写,再往深处的小伙伴可以看到Map里面的Entry是弱引用,既然说到了弱引用,那就要考虑到在YGC的时候是否被回收掉,虽然Entry是弱引用,但Entry的key 是ThreadLocal,我们是用new 来声明了一个强引用ThreadLocal,这时YGC时还会回收Entry吗?小伙伴们可以自己思考下