ThreadLocal源码理解
1、ThreadLocal是如何实现线程间数据隔离的
下面是 ThreadLocal类里 set 方法的源码,我们可以从这个方法去理解实现线程间数据隔离的原理。
// ThreadLocal 类
public void set(T value) {
// 1、获取当前线程
Thread t = Thread.currentThread();
// 2、获取当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 3、往当前对象的ThreadLocalMap对象里设置值
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap map = getMap(t)
这句话是 ThreadLocal 实现线程隔离的关键。
下面我们来看看 getMap() 方法实现
ThreadLocalMap getMap(Thread t) {
// 这里返回的是当前线程的threadLocals属性
return t.threadLocals;
}
因为每个线程来 set 属性的时候,都是返回各个线程自己的 threadLocals 的,然后将数据保存到当前线程的threadLocals变量中,所以,自然就实现了线程隔离。
2、为什么 ThreadLocalMap 的 key 是弱引用
前面我们说了,ThreadLocal 在调用 set 方法的时候,是将值设置到了各自线程的 threadLocals 里。那么,threadLocals 里是如何存储的呢?首先,我们很容易想到,一个线程里,肯定不可能只存储一个ThreadLocal类型的对象,所以,线程的 threadLocals 里肯定是有一个数组,这个数组的里的每一项,便是一个 ThreadLocal 的实例对象。
// 这是Thread类中,threadLocals的声明语句
ThreadLocal.ThreadLocalMap threadLocals = null;
接下来,我们就来验证一下我们上面的猜想,去看一下 ThreadLocal.ThreadLocalMap的代码:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 保存当前线程的所有ThreadLocal实例的数组
private Entry[] table;
// ... 省略其它代码 ...
}
- 为什么 ThreadLocalMap 的 key 是弱引用
理解了线程是如何存储ThreadLocal的实例之后,我们再回到主题上来,为什么 ThreadLocalMap 的 key 是弱引用?
java 中,引用分为 强、软、弱、虚 四种引用。弱引用的意思是,如果一个对象只有弱引用指向它,只要遭遇gc就会被回收。
我们试想一下,如果 ThreadLocalMap 的 key 是强引用,当引用 ThreadLocal 的那个对象都被回收,但是 ThreadLocalMap 还持有 ThreadLocal 的强引用,那么这个 ThreadLocal 便不会被回收,这样就会导致内存泄漏。
3、如何避免ThreadLocal 的内存泄漏
上面我们说了,在 Entry 中,key设置为弱引用,是为了防止 key 的内存泄漏。但是,我们想想,如果引用 ThreadLocal 的那个对象被回收了,也就是说 Entry 中的key 也会被回收,但是 Entry 中的value又是一个强引用啊。总的来说就是,当引用 ThreadLocal 的对象被回收时,Entry中的key会被回收,而value不会,这样就出现了内存泄漏,那么,我们该如何避免这种内存泄漏呢,其实很简单,我们只要在使用ThreadLocal 的时候,如果不用了,一定记得调用 ThreadLocal 的 remove 方法,将这个 value 移除掉就可以了。
4、ThreadLocal 是如何解决 Hash 冲突的
我们刚才提到,threadLocals 里,是用一个 Entry的数组来存储 ThreadLocal 的实例的,在存储的时候,根据 Hash 判断 ThreadLocal 实例在数组中的位置,既然是根据 Hash 来确定位置的,就难免会发生 Hash 冲突,那么 ThreadLocal 是如何解决 Hash 冲突的呢?我们来看一看 ThreadLocal 的私有set方法就知道了。
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 采用线性探测的方式来解决hash冲突
// 如果发现这个位置上已经被其他的 key 值占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 找到key对应的 ThreadLocal ,便修改对应的值
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
本文地址:https://blog.csdn.net/liukunc9/article/details/109922570
推荐阅读
-
Spring5源码解析5-ConfigurationClassPostProcessor (上)
-
Java 集合系列(四)—— ListIterator 源码分析
-
spring5 源码深度解析----- AOP代理的生成
-
libmxml数据结构(源码分析)
-
Spring5源码解析4-refresh方法之invokeBeanFactoryPostProcessors
-
springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂
-
jQuery插件HighCharts绘制的2D堆柱状图效果示例【附demo源码下载】
-
jQuery插件HighCharts实现的2D堆条状图效果示例【附demo源码下载】
-
深圳高一度网络:SEO优化中应理解搜索引擎的目的
-
phpize的深入理解