ThreadLocal源码学习
CVTE一面有问过这个问题,问我ThreadLocalMap如何保证了线程之间存储元素的隔离性,我还记得我当时很搞笑的说哦synchronized。。。。。(现在感觉基础不扎实),不废话了
ThreadLocal:保证每个线程都有自己的一片空间(就是个map),而这些空间之间具有隔离性,其他线程在高并发情况下不能访问(自己的理解)
如何保证隔离性呢,我们走近源码看看:(分析的不好还望指出小白的不足)
导读(大概先知道ThreadLocal是什么,里面有什么,需要依赖什么类或者接口吗)
首先ThreadLocal是一个类
我们主要从它的几个重要的方法来说,如get、set、remove方法,它的内部是由一个ThreadLocalMap内部类来实现,所以你的数据最终都存到了这个Map里,一想到Map存储结构,你一定会想到key和value,对,没错,你在ThreadLocal里存储值的时候他的键(key)就是以当前的threadlocal为键值的。(先这样说,之后再理解)
其底层是由一个Entry数组来实现,可以直接看出,这个数组又继承于虚引用这个类,所以我们就会想到会不会存在垃圾回收方面的问题呢?先抛出这样的一个引子
接下来我们从几个方法来了解一下ThreadLocal内部情况
set方法
作为set方法,首先先获取到当前的线程,也就是你要把值放在哪个线程的Map里面,接着以当前的线程获取当前线程下的map,如果当前线程如果为空,那么就新建一个线程
这里你可能你不明白这个this指的是什么,其实这个this就是当前线程的threadlocal,因为你要创建一个ThreadLocalMap,你得知道你是哪个threadlocal下面的map,所以这里将threadlocal传进ThreadLocalMap构造函数中。创建好的threadlocalMap当然得赋值给当前线程的threadlocals这个map对象。
如果不为空呢?我们接着看下map.set(this.value);干了什么?
刚也说了键,你还有印象吧,这里这个传入的this就是threadlocal,下面开始说一下这个方法:
首先拿到当前ThreadLocalMap里的 private Entry[] table;,然后计算你要存储这个值的位置在哪里,也就是通过这个for循环来遍历
如果出现hash冲突,通过nextIndex这个方法计算要存储的位置,也就是就将冲突的位置加一,然后再存。
如果你的数组为空的话,退出循环新建一个Entry数组,并将你要传入的key和value传入进去。
如果不为空呢那么就获取这个key上的引用,
我们重点来说我红色标记的这句话,如果不为空,那么就先拿到这个key的引用,如果这个引用就是你当前threadlocal为key,那么就返回这个结果,如果为空呢?为什么会为空,由于Entry的key是继承了弱引用,在下一次GC时不管它有没有被引用都会被回收掉,而value没有被回收。如果为null,就调用replaceStaleEntry()方法接着循环寻找相同的key,如果存在,直接替换旧值,如果不存在,则在当前位置上重新创建新的Entry,避免了内存泄漏。
get方法
一样,先获取当前线程,然后获取当前线程下面的ThreadLocalMap
如果有相同的key,则直接将值赋给当前这个key,然后返回。
如果不相同或者key为空,则一直继续向后找:
注意:if(k==null)这句,清除key=null为空的entry对象
remove方法就不说了,自行下去可以想想
使用remove可以解决内存泄露问题,手动的清除key为null的值,因为可能你没有使用get、set、remove等方法。