文章目录
ThreadLocal
- ThreadLocal作用是存储线程局部变量。空间解决并发共享变量冲突问题,为
每个线程
分别持有自己的存储空间,存储相应的值(分身独有
)。
源码
ThreadLocal类内部常用方法:
// ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class.
ThreadLocal.ThreadLocalMap threadLocals = null;
// 拿到线程持有的ThreadLocal.ThreadLocalMap(持有Entry[],其Key为ThreadLocal实例,是弱引用。Value为线程独有存储空间,即存储的对应的值。)
// 下面的 this 为 ThreadLocal 实例引用。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
public T get() {
Thread t = Thread.currentThread(); // 当前线程
ThreadLocalMap map = getMap(t); // 获取当前线程的 ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
static class ThreadLocalMap{
static class Entry extends WeakReference<ThreadLocal<?>>{
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k); // key 是 ThreadLocal 实例引用,value 是线程独有变量值。
value = v;
}
}
}
- 备注:ThreadLocalMap
实际上 是对 其持有的 table 的控制
(private Entry[] table),包括扩容、hash计算等。table 的本质是 当前线程 独有的线程局部变量。Entry 的 key 是 ThreadLocal<?> key 实例,value 是对应的局部变量的值。
源码分析:
- Thread 持有 ThreadLocalMap(并不是map) 持有 Entry[] , Entry(key:ThreadLocal 为弱引用, value:Object 为变量值需要使用方手动移除) ,
每个 Thread 可以有多个 ThreadLocal 变量,所以Entry[]的下标是通过 threadLocalHashCode 计算来的。
- ThreadLocal 有
静态内部类 ThreadLocalMap
,ThreadLocalMap 有静态内部类 Entry
。 Entry 继承 WeakReference(虚引用,垃圾回收每次都清理)
,目的是:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. However, since reference queues are not used, stale entries are guaranteed to be removed only when the table starts running out of space.- Thread 强引用 ThreadLocalMap,ThreadLocalMap 强引用 Entry 数组(private Entry[] table;)。
- 相当于每个 Thread 都独自维护了一个数组 table,其中的每个Entry元素,key 是 ThreadLocal 实例引用,value 是线程独有变量值。
应用场景:
- 数据库连接的时候经常用。
- 普通的数据库连接的管理类:有一个很严重的问题,
客户端频繁的使用数据库,会导致多次建立和关闭连接
,服务器可能会吃不消。 - 最好使用ThreadLocal,因为
ThreadLocal在每个线程中对连接会创建一个副本,且在线程内部任何地方都可以使用,线程之间互不影响
,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
重要思想:
-
简单来想,如果实现每个线程都有自己的局部变量,ThreadLocal 可以内部直接维护一个 hashMap,key 是线程引用,value 是线程对应的局部变量值。但是,此种方案,相当于 ThreadLocal 维护所有线程的局部变量,是一种
集中管控的方式,效率不高
。(集中管控) -
ThreadLocal 的本质是想 让每一个线程都独有自己的局部变量,不是分身,不是副本,不是备份,而是属于线程自己独有的局部变量。既然如此,
局部变量应该由线程自己维护
。(分权思想) -
真实的实现,也的确如此。每个线程 持有 ThreadLocalMap,每个
ThreadLocalMap 管控 Entry 数组
,每个 Entry 存储局部变量,其中 Entry 数组的下标,通过 ThreadLocal 实例引用计算映射(下标计算),每个 Entry 的 key 为 ThreadLocal 实例(此处的 key 主要是用于 将 ThreadLocal实例 声明为弱引用)
,每个 Entry 的本质实际上就是“局部变量值”,每个 ThreadLocal 实例 对应 一个 Entry 中的值,映射逻辑 为下标计算。 -
所以,ThreadLocal 获取对应线程的步骤是:
- 获取当前线程:Thread t = Thread.currentThread();
- 获取当前线程持有的 ThreadLocalMap map = getMap(t);
- 通过当前 ThreadLocal 引用 计算 table(Entry数组)下标 获得 线程独有局部变量值,ThreadLocalMap.Entry e = map.getEntry(this);
-
注意事项:由于长时间运行的线程没有从线程本地映射中清除值,我们仍然面临内存泄漏的可能性。
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } /** * Remove the entry for key. */ private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; // 找到当前key对应的数组下标。 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; } } }
-
备注:ThreadLocal实例声明为弱引用,会被JVM垃圾回收(每次,且其销毁地点必定为伊甸区),但是
每个线程持有的 ThreadLocalMap(是对Entry[]数组的控制)是强引用
,所以需要把对应线程的 tab(Entry[]数组)中,ThreadLocal实例对应的线程独有局部变量值清理掉
。
table的下标是如何计算的?
- 预备知识:装填因子 = hash表中的数据项和表长的比例。通常hash表数组长度是一个质数:假设hash表的长度不是一个质数为15,那么对于那些映射在数组单元0的数字步长为5,就会探索在0->5->10->0->5->10->0一直循环下去,算法只能尝试着三个位置,这是达不到要求。如果数组容量是11(是一个质数),这样就可以探测所有单元,0->5->10->4->9->3->8->2->7->1->6->0。采用质数作为数组容量会保证探测到每一个单元。
- Hash冲突->开放地址法:
- 线性探测:顺延一个一个找。数据超过2/3满时,性能下降。-> 缺点:数据聚集。
- 二次探测:避免聚集的尝试,相隔比较远的单元进行探测,而不是线性一个一个的探测。比如: 二次探测是过程是x+1,x+4,x+9,以此类推。二次探测的
步数是原始位置相隔的步数的平方
。缺点:都映射到10,然后探测11、14、19,即还有可能导致聚集问题。 - 再哈希法:再哈希是把关键字用不同的哈希函数再做一遍哈希化,用这个结果作为步长。虽然不同的关键字可能会映射到相同的数组单元,但是可能会有不一样的探测步长。ThreadLocal 中,Thread 持有 ThreadLocalMap 持有 Table(Entry数组),需要计算数组下标。数组下标的计算是通过 ThreadLocal实例 持有的 threadLocalHashCode 成员变量 取模映射的。在 hash 映射过程中,需要避免碰撞:第一种:同一个 ThreadLocal 实例,向 ThreadLocal 变量中设置多个值的时产生的碰撞,此时采用的是 线性探测(linear-probe)。第二种:多个 ThreadLocal 实例的时候,最极端的是每个线程都new一个ThreadLocal实例,此时利用特殊的哈希码0x61c88647大大降低碰撞的几率。同时利用开放地址法处理碰撞。
0x61c88647这个魔数的选取与斐波那契散列有关
,0x61c88647对应的十进制为1640531527(黄金比例)
。斐波那契散列的乘数可以用(long) ((1L << 31) * (Math.sqrt(5) - 1))可以得到2654435769,如果把这个值给转为带符号的int,则会得到-1640531527。换句话说:(1L << 32) - (long) ((1L << 31) * (Math.sqrt(5) - 1))得到的结果就是1640531527也就是0x61c88647
,目的是为了让哈希码能均匀的分布在2的N次方的数组里
。
静态内部类的设计意图?
- 多继承中,当多个父类有重复的属性或者方法,会导致子类调用结果含糊不清。所以,Java是单继承的,一个类只能继承另一个具体类或者抽象类,可以实现多个接口。接口解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。每个内部类都能独立地继承一个(具体类或者抽象类)实现,所以无论外围类是否已经继承了某个(具体类或者抽象类)实现,对于内部类都没有影响。非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。
ThreadLocal 仅仅是提供了内部类,也就是 table,而具体的持有与引用是在每个 Thread 上。
ThreadLocal Value为什么不是弱引用?
- 强调一下,ThreadLocal 提供 内部类 ThreadLocalMap,ThreadLocalMap 持有 Table(Entry数组),Entry的key为ThreadLocal的引用(弱引用),Value为强引用。目的是:将value为Null的Entity直接清除(伊甸区清理)。假如value被设计成弱引用,那么很有可能当你需要取这个value值的时候,取出来的值是一个null。无论key是强引用还是弱引用,threadLocal都必须要在代码逻辑执行完毕后调用remove()将value的强引用删掉,否则就会导致内存泄漏。也就是说,
ThreadLocal不需要开发者关心key的回收问题,开发者只需要关心自己操作的value的回收问题即可。内部的归内部管理,外部的归外部管理,各司其职。
Reference
-
https://blog.csdn.net/zqixiao_09/article/details/79265789(Linux 网络协议栈之内核锁(五)—— 自旋锁在抢占(非抢占)单核和多核中的作用)
-
https://www.iteye.com/topic/103804(正确理解ThreadLocal)
-
https://www.cnblogs.com/ilellen/p/4135266.html(ThreadLocal 和神奇的数字 0x61c88647)
-
https://www.javaspecialists.eu/archive/Issue164-Why-0x61c88647.html(Why 0x61c88647?)
-
https://www.zhihu.com/question/399087116(threadlocal value为什么不是弱引用?)
-
https://www.jianshu.com/p/cf57726e77f2(三大性质总结:原子性,有序性,可见性)
-
https://www.jianshu.com/p/94ba4a918ff5(InheritableThreadLocal详解)
-
https://www.jianshu.com/p/e0774f965aa3(TransmittableThreadLocal详解)
-
https://www.php.cn/java-article-411446.html(ThreadLocal是什么?ThreadLocal的原理分析)图不错!