前言:ThreadLocalMap是ThreadLocal的内部类
简介
ThreadLocalMap其本质是一个数组,使用ThreadLocal为key,储存线程数据,每一个线程都有一个ThreadLocalMap对象
ThreadLocalMap有一个静态内部类Entry,Entry类继承WeakReference弱引用,当JVM进行垃圾回收时,无论内存是否充足,其指向的对象实例会被回收掉,即活不过一次GC
1 2 3 4 5 6 7 8 static class Entry extends WeakReference <ThreadLocal <?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super (k); value = v; } }
字段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private static final int INITIAL_CAPACITY = 16 ;private Entry[] table;private int size = 0 ;private int threshold;
构造方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1 ); table[i] = new Entry(firstKey, firstValue); size = 1 ; setThreshold(INITIAL_CAPACITY); } private ThreadLocalMap (ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0 ; j < len; j++) { Entry e = parentTable[j]; if (e != null ) { @SuppressWarnings ("unchecked" ) ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null ) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1 ); while (table[h] != null ) h = nextIndex(h, len); table[h] = c; size++; } } } }
cleanSomeSlots 清除一些无效槽(slot是槽的意思,这里就是指数组的某个位置),无效指entry!=null但是entry指向的引用为null,set方法、replaceStaleEntry方法会调用。
需要注意的cleanSomeSlots执行log2(n),如同方法名,只会去除some一些无效槽,只是考虑到了时间效率上的平衡。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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; }
expungeStaleEntries 循环遍历找到key==null的index,然后调用 expungeStaleEntry进行段清理
1 2 3 4 5 6 7 8 9 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); } }
expungeStaleEntry 段清理,清理key==null的数据,并将数据‘前移’
为什么说段?该方法清理掉当前无效entry后,继续向后遍历直到遇到null entry
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private int expungeStaleEntry (int staleSlot) { Entry[] tab = table; int len = tab.length; tab[staleSlot].value = null ; tab[staleSlot] = null ; size--; Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null ; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null ) { e.value = null ; tab[i] = null ; size--; } else { int h = k.threadLocalHashCode & (len - 1 ); if (h != i) { tab[i] = null ; while (tab[h] != null ) h = nextIndex(h, len); tab[h] = e; } } } return i; }
getEntry 根据threadLocalHashCode对表长度的取模获得index
如果index对应的slot就是要获取的threadLocal,则直接返回结果。否则调用getEntryAfterMiss进行线性检测
1 2 3 4 5 6 7 8 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); }
getEntryAfterMiss 由getEntryAfterMiss方法调用,当getEntry没有找到时,会从取模得到index开始线性检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private Entry getEntryAfterMiss (ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; 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 ; }
nextIndex 获取下一个index,注意是环形
1 2 3 private static int nextIndex (int i, int len) { return ((i + 1 < len) ? i + 1 : 0 ); }
prevIndex 获取前一个index,注意是环形
1 2 3 private static int prevIndex (int i, int len) { return ((i - 1 >= 0 ) ? i - 1 : len - 1 ); }
rehash 先清除key==null的数据,然后阈值判断,确实是否要扩容,由set方法调用
1 2 3 4 5 6 7 8 9 private void rehash () { expungeStaleEntries(); if (size >= threshold - threshold / 4 ) resize(); }
remove 通过key清除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 ; } } }
replaceStaleEntry 由set方法调用,进行无效数据的清除。这个方法是ThreadLocalMap最复杂的,简单说就是环形清除,将有效的entry更紧密,然后调用cleanSomeSlots清除,从而提高效率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 private void replaceStaleEntry (ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null ; i = prevIndex(i, len)) if (e.get() == null ) slotToExpunge = i; for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null ; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return ; } if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } tab[staleSlot].value = null ; tab[staleSlot] = new Entry(key, value); if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }
resize 由rehash方法调用,扩容2倍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 private void resize () { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2 ; Entry[] newTab = new Entry[newLen]; int count = 0 ; for (int j = 0 ; j < oldLen; ++j) { Entry e = oldTab[j]; if (e != null ) { ThreadLocal<?> k = e.get(); if (k == null ) { e.value = null ; } else { int h = k.threadLocalHashCode & (newLen - 1 ); while (newTab[h] != null ) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; }
set set设置一个新值,如果key找不到就会将值插入到一个新的entry
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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) { 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(); }
setThreshold 设置阈值为len的三分之二,也就是负载因子
1 2 3 private void setThreshold (int len) { threshold = len * 2 / 3 ; }
总结 ThreadLocalMap是ThreadLocal的静态内部类,定义了一个继承弱引用的Entry内部类,泛型是ThreadLocal,用于储存线程,方法逻辑其实很简单,使用环形数组与线性探测法避免冲突,实现Map,核心是get、set方法,然后2/3的负载因子,扩容2倍。
ThreadLocal是一个线程本地变量隔离作用的工具类,当线程运行结束,需要GC,所以使用弱引用,但是这有一个内存泄漏的隐患,即Entry!=null&&key==null的情况,value中存储了大量数据,为了避免这种情况,有大量的代码用来进行清除无效数据,cleanSomeSlots、expungeStaleEntries、expungeStaleEntry、replaceStaleEntry等方法就是为了实现这个功能参考
一篇文章,从源码深入详解ThreadLocal内存泄漏问题