Java中的ThreadLocalMap源码解读
概述
ThreadLocalMap是ThreadLocal的内部类,是一个key-value数据形式结构,也是ThreadLocal的核心。
ThreadLocalMap中数据是存储在Entry类型数组的table中的,Entry继承了WeakReference(弱引用),注意key是弱引用,vlaue不是。
源码解读
1.成员变量
/** * 初始容量 */ private static final int INITIAL_CAPACITY = 16; /** * ThreadLocalMap数据真正存储在table中 */ private Entry[] table; /** * ThreadLocalMap条数 */ private int size = 0; /** * 达到这个大小,则扩容 */ private int threshold; // 默认为0
2.threadLocalHashCode
private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
HASH_INCREMENT = 0x61c88647是一个魔法数,可以减少hash冲突,通过nextHashCode.getAndAdd(HASH_INCREMENT)方法会转化为二进制数据,主要作用是增加哈希值,减少哈希冲突
3.构造函数
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //初始化table数组,INITIAL_CAPACITY默认值为16 table = new Entry[INITIAL_CAPACITY]; //key和16取得哈希值 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //创建节点,设置key-value table[i] = new Entry(firstKey, firstValue); size = 1; //设置扩容阈值 setThreshold(INITIAL_CAPACITY); }
4.set
private void set(ThreadLocal<?> key, Object value) { 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)]) { ThreadLocal<?> k = e.get(); if (k == key) { //如果key是相同,则替换,并return e.value = value; return; } if (k == null) { //e!=null,key==null,因为key是弱引用,所以key已经被gc回收了,replaceStaleEntry方法就是用来解决内存泄露问题 replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; int slotToExpunge = staleSlot; //prevIndex是指针向前,寻找前面过期数据 for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; //向后寻找key相同的数据 for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; //通过和过期的slot进行交换,维护哈希表顺序 tab[i] = tab[staleSlot]; tab[staleSlot] = e; if (slotToExpunge == staleSlot) slotToExpunge = i; //清除过期slot cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // 如果key并没有在map中出现过,则直接创建 tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); //如果还有其他过期slot,则清除 if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); } private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; if (e != null && e.get() == null) { n = len; removed = true; i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0); return removed; } private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // 删除下标为staleSlot的slot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // 重新哈希,直到遇到null Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); //如果key==null,说明已经被回收 if (k == null) { //Entry设置为null,size减一 e.value = null; tab[i] = null; size--; } else { //重新进行hash计算 int h = k.threadLocalHashCode & (len - 1); //如果计算的位置和从前位置不一致 if (h != i) { tab[i] = null; //扫描到null,将值放入 while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; } private void rehash() { expungeStaleEntries(); //如果当前size大于法制的四分之三,则扩容 if (size >= threshold - threshold / 4) resize(); } /** * 全局清理 */ private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; for (int j = 0; j < len; j++) { Entry e = tab[j]; if (e != null && e.get() == null) expungeStaleEntry(j); } }
set方法首先根据key计算存储位置 如果计算出来的下标不为空,会进入循环,循环内如果key相同,则直接替换,如果key被回收,则调用replaceStaleEntry方法清除,并且在该方法中设置value。 如果计算出来的下标为空,则直接设置值,并在最后通过cleanSomeSlots清除过期key和确定是否通过rehash扩容。
5.getEntry
private Entry getEntry(ThreadLocal<?> key) { //计算下标位置 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; //没有hash冲突,entry存在,并且key未被回收 if (e != null && e.get() == key) return e; else //hash冲突,通过线性探测查找,可能查询到 return getEntryAfterMiss(key, i, e); } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; //循环查找,直到为null while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) //被回收了,清除 expungeStaleEntry(i); else //循环下一个 i = nextIndex(i, len); e = tab[i]; } return null; }
getEntry是根据ThreadLocal获取ThreadLocalMap中某个值的,如果存在哈希冲突则通过getEntryAfterMiss方法线性探测查找
6.remove
private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //如果threadLocalHashCode计算出的下标找到的key和传入key不同,则证明出现哈希冲突,则循环向下查找 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { //如果key相同 if (e.get() == key) { //删除当前Entry e.clear(); //清理 expungeStaleEntry(i); return; } } }
总结
1.ThreadLocalMap.Entry继承了WeakReference,实现了弱引用,提高了垃圾回收的效率。
2.ThreadLocalMap可能存在内存泄露,因为key被回收后,但是value依然和Entry存在强引用关系,所以使用完进行remove是一个很好的习惯,可以避免内存泄露。
到此这篇关于Java中的ThreadLocalMap源码解读的文章就介绍到这了,更多相关ThreadLocalMap源码解读内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
完美解决Logback configuration error detected的问题
这篇文章主要介绍了完美解决Logback configuration error detected的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-08-08Spring Boot中的 6 种API请求参数读取方式示例详解
使用Spring Boot开发API的时候,读取请求参数是服务端编码中最基本的一项操作,Spring Boot中也提供了多种机制来满足不同的API设计要求,这篇文章主要介绍了Spring Boot中的 6 种API请求参数读取方式示例详解,需要的朋友可以参考下2024-05-05javaweb用户注销后点击浏览器返回刷新页面重复登录问题的解决方法
这篇文章主要为大家详细介绍了javaweb用户注销后点击浏览器返回刷新页面重复登录问题的解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下2016-09-09Linux+Docker+SpringBoot+IDEA一键自动化部署的详细步骤
这篇文章主要介绍了Linux+Docker+SpringBoot+IDEA一键自动化部署的详细步骤,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2020-07-07
最新评论