小知识,大挑战!本文正在参与“ 程序员必备小知识 ”创作活动
本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金
Code皮皮虾 一个沙雕而又有趣的憨憨少年,和大多数小伙伴们一样喜欢听歌、游戏,当然除此之外还有写作的兴趣,emm...,日子还很长,让我们一起加油努力叭🌈
前言
博主 常年游荡于牛客面经区,总结了字节、阿里、百度、腾讯、美团等等大厂的高频考题,之后会逐步分享给大家,期待各位的关注、点赞!
什么是ThreadLocal?
ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰
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底层原理
点开 ThreadLocal 的 set()方法
- 首先是获取到当前线程
- 调用getMap(t)方法获取到 ThreadLocalMap
- 如果map不为null则进行set操作,如果为null则进行创建map操作
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<ThreadLocal<?>>==
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 的键值对。
ThreadLocal相关面试题
ThreadLocal 如何解决 Hash 冲突?
与 HashMap 不同,ThreadLocalMap 结构非常简单,没有 next 引用,也就是说 ThreadLocalMap 中解决 Hash 冲突的方式并非链表的方式,而是采用线性探测的方式。所谓线性探测,就是根据初始 key 的 hashcode 值确定元素在 table 数组中的位置,如果发现这个位置上已经被其他的 key 值占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
使用CAS,每次增加固定的值,所以采用的是线性探测法解决HasH冲突
经典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内存泄漏问题及解决办法
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为不可回收对象
❤最后
我是 Code皮皮虾,一个热爱分享知识的 皮皮虾爱好者,未来的日子里会不断更新出对大家有益的博文,期待大家的关注!!!
创作不易,如果这篇博文对各位有帮助,希望各位小伙伴可以==一键三连哦!==,感谢支持,我们下次再见~~~
近期评论