我妻善逸:雷之呼吸
ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰
ThreadLocl使用比较简单,主要有三个方法:get()、set()、remove()
相信不用我多说各位小伙伴也知道是什么意思
public class Test01 {
public static void main(String[] args) {
ThreadLocal<Integer> local = new ThreadLocal<>();
local.set(10);
System.out.println(local.get());
local.remove();
System.out.println(local.get());
}
}
点开 ThreadLocal 的 set()方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
点开get()方法
跟HashMap差不多,小伙伴们自己看看源码绝对能懂
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();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
点开remove()方法
跟HashMap差不多,找到索引,遍历又对因key就删除
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
细心的小伙伴可能就发现了这三个方法中都需要去获取:当前Thread和ThreadLocalMap
那么这是为什么呢???
我们接着看源码,点开getMap()方法
可以发现,ThreadLocalMap获取的是Thread中的一个对象
相信到这大家就明白了 ThreadLocal是怎么做到各个线程互不干扰的把
因为获取到的是当前线程的ThreadLocalMap,各个线程所以互不干扰
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
小伙伴们呢可别觉得已经掌握了,还没完呢,这才哪到哪,,我们接着看
打开ThreadLocalMap的set方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//hash获取对应下标,之后会讲
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//封装为Entry节点
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
看过上面源码,熟悉HashMap的小伙伴们可能就发现了一个熟悉的身影——>Entry
但这个Entry就跟HashMap的有所不同,它继承了 WeakReference
WeakReference:弱引用
而上面还有段代码不知道小伙伴们注意到没:tab[i] = new Entry(key, value);
这段代码意味着什么相信也不用我多说了吧,就是将key,value封装为 Entry节点;再根据map.set(this, value);
这段代码可知,Key为当前的ThreadLocal对象,value为我们要set进来的值
但是这里又有所重点:那就是对key调用了super(k)
,这里我暂且不多说,涉及到ThreadLocal的一个经典面试题,文章后面会进行详细讲解,小伙伴们要有耐心继续看哦!!!
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
到此基本结束,不要奇怪,ThreadLocal其实并不是太难,那我们先来做个总结!
我们set进去的值,最终是放在了当前线程的 ThreadLocalMap
中,并不是存在 ThreadLocal
上,ThreadLocal
可以理解为只是ThreadLocalMap
的封装,传递了变量值。 ThrealLocal
类中可以通过Thread.currentThread()
获取到当前线程对象后,直接通过getMap(Thread t)
可以访问到该线程的ThreadLocalMap
对象。
每个Thread
中都具备一个ThreadLocalMap
,而ThreadLocalMap
可以存储以ThreadLocal
为 key ,Object 对象为 value 的键值对。
与 HashMap 不同,ThreadLocalMap 结构非常简单,没有 next 引用,也就是说 ThreadLocalMap 中解决 Hash 冲突的方式并非链表的方式,而是采用线性探测的方式。所谓线性探测,就是根据初始 key 的 hashcode 值确定元素在 table 数组中的位置,如果发现这个位置上已经被其他的 key 值占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
使用CAS,每次增加固定的值,所以采用的是线性探测法解决HasH冲突
经典CAS
不懂CAS的可以看我这篇,写的超详细
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被 Entry 中的 Key 引用的,因此如果 ThreadLocal 没有外部强引用来引用它,那么 ThreadLocal 会在下次 JVM 垃圾收集时被回收。这个时候 Entry 中的 key 已经被回收,但是 value 又是一强引用不会被垃圾收集器回收,这样 ThreadLocal 的线程如果一直持续运行,value 就一直得不到回收,这样就会发生内存泄露。
解决办法
1. 使用完后记得remove
2.ThreadLocal自己提供了这个问题的解决方案。
每次操作set、get、remove操作时,会相应调用 ThreadLocalMap 的三个方法,ThreadLocalMap的三个方法在每次被调用时 都会直接或间接调用一个 expungeStaleEntry() 方法,这个方法会将key为null的 Entry 删除,从而避免内存泄漏。
3. 使用static修饰ThreadLocal
还可以使用 static 修饰ThreadLocal,保证ThreadLocal为强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。
原因:根据可达性算法分析,类静态属性引用的对象可作为GC Roots根节点,即保证了ThreadLocal为不可回收对象
我是 CRUD大师,一个热爱分享知识的 皮皮虾爱好者,未来的日子里会不断更新出对大家有益的博文,期待大家的关注!!!
创作不易,如果这篇博文对各位有帮助,希望各位小伙伴可以一键三连哦!,感谢支持,我们下次再见~~~
分享大纲
大厂面试题专栏
Java从入门到入坟学习路线目录索引
开源爬虫实例教程目录索引
更多精彩内容分享,请点击 Hello World (●’◡’●)