ThreadLocal和ThreadLocalMap的源码分析

最近两天刚好马上要出去找工作面试了,之前一直听说过ThreadLocal,知道它是为每个线程分配一块空间,但是不知道具体它的内部是怎么实现的,今天就看看它的源码,看看它是怎么实现的:

Thread类:

ThreadLocal和ThreadLocalMap的源码分析_第1张图片

Thread类的内部持有ThreadLocalMap的引用,接着看ThreadLocal类:

ThreadLocal类:

先来看看ThreadLocal中的set方法(注意以下代码都是ThreadLocal类中的,并不涉及ThreadLocalMap类中的内容,你完全可以把ThreadLocalMap看成一个普通的map,其实它就是一个普通的map):

ThreadLocal和ThreadLocalMap的源码分析_第2张图片

以上是ThreadLocal类中的get和set方法,我们先看set方法,传入的参数就是我们要保存的value,然后它获取了当前执行的线程对象,然后用当前线程对象获取到了ThreadLocalMap对象并赋值给map。然后判断map是否为空,如果map为空,那么就创建map,如果map不为空,那么就把(this,value)键值对存入map。注意这里仅仅是通过当前线程对象获取到了ThreadLocalMap对象还没有取到map里面的值,所以这个线程对象不是map的key,好多博客里面都说ThreadLocalMap的key是线程对象,这个说法是错误的,从方法名也可以看出来,getMap是获取map对象,getValue才是获取值,getValue/setValue传入的才是key。可以看到map.set方法传入的是(this,value),那么显然这里的this就是map的key了,那么this是什么呢?当然就是调用方法的对象了,也就是我们自己创建的ThreadLocal对象了。所以ThreadLocalMap中的key不是线程对象而是我们自己创建的ThreadLocal对象。接着看看getMap方法干嘛了:

getMap方法就是很简单的返回了线程的threadLocals属性。查看Thread类发现在Thread类中并没有对这个属性的操作,仅仅是持有了这个ThreadLocalMap的引用并把null值赋给它,但是没有对它进行其他的操作,开始看到这里我有点懵逼了,你弄了个成员变量你又不用它,那你创建它干嘛呢?接着往下看,发现给这个引用创建对象的操作是在ThreadLocal类中完成的:

也就是这个createMap方法创建了ThreadLocalMap对象并把值赋值给线程中的threadLocals属性。这里发现ThreadLocalMap的key是this,也就是ThreadLocal对象,它才是map的key,而map的值肯定就是我们要存储的value啦。但是好像我们在使用ThreadLocal的时候并没有调用这个createMap方法啊,那是谁调用了它呢?

ThreadLocal和ThreadLocalMap的源码分析_第3张图片

哦,原来是setInitialValue方法调用了它,那么我们在使用的时候好像也没有调用setInitialValue方法啊,而且这个方法是private的,你想用也用不了啊,继续看谁调用了setInitialValue方法:

ThreadLocal和ThreadLocalMap的源码分析_第4张图片

搞了半天原来是get方法调用了setInitialValue方法啊,然后setInitialValue方法调用了createMap方法,然后createMap方法创建了ThreadLocalMap对象并把它赋值给线程Thread类中的threadLocals属性。接着我们来看看get方法干嘛了,同样也是先获取当前线程对象,然后用当前线程对象获取到了map对象,然后在取值之前判断一下map是不是为空,如果map不为空就取值,如果map为空,那么就调用setInitialValue方法进行初始化创建一个map。

好了,总结一下上面这波操作,线程Thread类中有一个threadLocals的属性,它的类型为ThreadLocalMap类,但是Thread类并没有给它创建对象,而是给它赋值为null。然后在我们自己在使用ThreadLocal时开始创建ThreadLocal对象,然后用ThreadLocal对象调用set和get方法存值和取值。存值和取值的过程中,先获取当前线程对象,然后用当前线程对象获取线程中的threadLocals属性。并且在一开始的时候map肯定为空,所以一开始会先创建ThreadLocalMap对象,然后将对象的值赋值给Thread类的threadLocals属性。这样set/get方法也就拿到了ThreadLocalMap的对象,拿到这个map对象之后就可以开心的存取value啦。

以上并不涉及ThreadLocalMap的源码,所以还是比较清晰简单的,ThreadLocalMap你可以把它就看作是一个普通的map,一会儿我们再来看它内部是怎么实现的。现在我们来看看为什么ThreadLocal能够为每个线程创建一个属于它的独立变量而不被其他线程所干扰呢?

我们可以看到在ThreadLocal中的set和get方法中会先获取当前线程对象,然后再获取这个线程对象的map。所以每个线程内部都会创建一个map,所以A线程一个map,B线程一个map,那么当然这两个map就不会互相干扰啦,你存取你的,我存取我的。就好比现在有两个房间(两个线程),每个房间里面都有一个箱子(map),但是这两个箱子用的是同一把锁同一把钥匙(ThreadLocal对象) ,现在你拿着这把钥匙分别去两个房间的箱子里面取东西,虽然是同一把钥匙,但是箱子不一样啊,箱子里面的东西也不一样啊,当然就不会拿错了(前提你别进错房间,除非你想当隔壁老王)。

因为我们在使用ThreadLocal的时候只创建了一个ThreadLocal对象(当然可以创建多个,我这里只是举个例子),所以多个线程用的是一个ThreadLocal对象,所以也就是说多个线程内部的map的key是一样的,都是我们创建的ThreadLocal对象。但是这一点也不影响啊,因为不是同一个map啊。当然这也说明了一点就是如果你只创建一个ThreadLocal对象,那么每个线程的map就只能存储一个value,为啥呢?因为map的key只能是你创建的这个ThreadLocal对象啊,你在存储的时候不能指定key啊,所以如果A线程中的map存储了x的值为10,你如果在set  x=12,那么后来的12会覆盖掉之前的10,因为同一个key嘛。那么你想存储多个value怎么办呢?很简单,创建多个ThreadLocal对象就可以了。

这里再细说一下,当你再创建一个ThreadLocal对象,那么你用这个对象set/get的时候,就会再去获取当前线程对象(和前一个map是同一个线程对象),然后再获取这个线程对象的threadLocals属性,注意这时候threadLocals是有值的,所以这时候直接存和取就行了,但是存和取的时候key变了,已经不是之前的那个ThreadLocal对象了,而是一个新的ThreadLocal对象了。所以如果是要存取多个变量,对于同一个线程而言是同一个map,不同的key,那么当然存取的值就不会覆盖了。对于不同的线程而言是不同的map,所以也不会存在存取错了的情况。

你可能感兴趣的:(java)