下面是 javaspecialists 中一篇文章对它的介绍:
This number represents the golden ratio (sqrt(5)-1) times two to the power of 31 ((sqrt(5)-1) * (2^31)). The result is then a golden number, either 2654435769 or -1640531527.下面用例子来证明下:
private static final int HASH_INCREMENT = 0x61c88647;public static void main(String[] args) throws Exception {int n = 5;int max = 2 << (n - 1);for (int i = 0; i < max; i++) {System.out.print(i * HASH_INCREMENT & (max - 1));System.out.print(" ");}}
运行结果为:0 7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25可以发现元素索引值完美的散列在数组当中,并没有出现冲突 。
ThreadLocalMap除了上述属性外,还有一个重要的属性 ThreadLocalMap,ThreadLocalMap 是 ThreadLocal 的静态内部类,当一个线程有多个 ThreadLocal 时,需要一个容器来管理多个 ThreadLocal,ThreadLocalMap 的作用就是管理线程中多个 ThreadLocal,源码如下:
static class ThreadLocalMap { /*** 键值对实体的存储结构*/ static class Entry extends WeakReference<ThreadLocal<?>> {// 当前线程关联的 value,这个 value 并没有用弱引用追踪Object value;/*** 构造键值对** @param k k 作 key,作为 key 的 ThreadLocal 会被包装为一个弱引用* @param v v 作 value*/Entry(ThreadLocal<?> k, Object v) {super(k);value = https://www.isolves.com/it/cxkf/bk/2020-07-29/v;} } // 初始容量,必须为 2 的幂 private static final int INITIAL_CAPACITY = 16; // 存储 ThreadLocal 的键值对实体数组,长度必须为 2 的幂 private Entry[] table; // ThreadLocalMap 元素数量 private int size = 0; // 扩容的阈值,默认是数组大小的三分之二 private int threshold;}
从源码中看到 ThreadLocalMap 其实就是一个简单的 Map 结构,底层是数组,有初始化大小,也有扩容阈值大小,数组的元素是 Entry,Entry 的 key 就是 ThreadLocal 的引用,value 是 ThreadLocal 的值 。ThreadLocalMap 解决 hash 冲突的方式采用的是线性探测法,如果发生冲突会继续寻找下一个空的位置 。这样的就有可能会发生内存泄漏的问题,下面让我们进行分析:
ThreadLocal 内存泄漏ThreadLocal 在没有外部强引用时,发生 GC 时会被回收,那么 ThreadLocalMap 中保存的 key 值就变成了 null,而 Entry 又被 threadLocalMap 对象引用,threadLocalMap 对象又被 Thread 对象所引用,那么当 Thread 一直不终结的话,value 对象就会一直存在于内存中,也就导致了内存泄漏,直至 Thread 被销毁后,才会被回收 。
那么如何避免内存泄漏呢?
在使用完 ThreadLocal 变量后,需要我们手动 remove 掉,防止 ThreadLocalMap 中 Entry 一直保持对 value 的强引用,导致 value 不能被回收,其中 remove 源码如下所示:
/** * 清理当前 ThreadLocal 对象关联的键值对 */public void remove() { // 返回当前线程持有的 map ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) {// 从 map 中清理当前 ThreadLocal 对象关联的键值对m.remove(this); }}
remove 方法的时序图如下所示:文章插图
remove 方法是先获取到当前线程的 ThreadLocalMap,并且调用了它的 remove 方法,从 map 中清理当前 ThreadLocal 对象关联的键值对,这样 value 就可以被 GC 回收了 。
那么 ThreadLocal 是如何实现线程隔离的呢?
ThreadLocal 的 set 方法我们先去看下 ThreadLocal 的 set 方法,源码如下:
【一文搞懂 ThreadLocal 原理】
/** * 为当前 ThreadLocal 对象关联 value 值 * * @param value 要存储在此线程的线程副本的值 */public void set(T value) { // 返回当前ThreadLocal所在的线程 Thread t = Thread.currentThread(); // 返回当前线程持有的map ThreadLocalMap map = getMap(t); if (map != null) {// 如果 ThreadLocalMap 不为空,则直接存储<ThreadLocal, T>键值对map.set(this, value); } else {// 否则,需要为当前线程初始化 ThreadLocalMap,并存储键值对 <this, firstValue>createMap(t, value); }}
set 方法的作用是把我们想要存储的 value 给保存进去 。set 方法的流程主要是:- 先获取到当前线程的引用
- 利用这个引用来获取到 ThreadLocalMap
- 如果 map 为空,则去创建一个 ThreadLocalMap
- 如果 map 不为空,就利用 ThreadLocalMap 的 set 方法将 value 添加到 map 中
文章插图
其中 map 就是我们上面讲到的 ThreadLocalMap,可以看到它是通过当前线程对象获取到的 ThreadLocalMap,接下来我们看 getMap方法的源代码:
推荐阅读
- 一文看懂静态资源服务沉浮及其在携程的演进
- 一文理解HDFS
- 一文带你搞懂前端本地存储
- 一文带你了解不一样的SQL,惊喜多多
- iOS|iOS 16前的最后一个版本!iOS 15.5登场:一文了解详情
- 一文读懂Redis的dict字典数据结构
- 新疆维吾尔自治区|一文带你了解和田玉“前世今生”
- HDMI 2.0已淘汰!HDMI 2.1上位:一文看懂新接口优势
- 索尼|PS5存储扩容需要注意啥?一文读懂
- 一文读懂AI计算机视觉技术