ThreadLocal
Posted lovezmc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal相关的知识,希望对你有一定的参考价值。
一、原理
ThreadLocal的原理就是将参数放在当前线程中,达到线程隔离的目的。
二、代码分析
1、 ThreadLocalMap
1.1 基础属性
// 默认容量 private static final int INITIAL_CAPACITY = 16; // ThreadLocalMap底层就是Entry数组,跟HashMap挺像的 private Entry[] table; // 实际的entry个数 private int size = 0; // 阈值,大于该值则扩容 private int threshold;
1.2 nextIndex、prevIndex
这里将Entry数组作为环形,数组最后一个值的下一个值为数组的第一个值。
// len = table.length
private static int nextIndex(int i, int len) return ((i + 1 < len) ? i + 1 : 0); // len = table.length private static int prevIndex(int i, int len) return ((i - 1 >= 0) ? i - 1 : len - 1);
1.3 set方法
将<ThreadLocal, Object>放进Entry数组中,并清除key为null的数据(这里涉及到弱引用问题,请转到 查看),最后,根据当前容量判断是否需要扩容。
注意,这里数组寻址方式和HashMap一样,都是 hashCode & (len-1),不同的是,如果出现hash碰撞,HashMap是将其放在同一个数组里,使用链表或红黑树存储;而这里出现hash碰撞时,则往后查找,找到一个空位将数据放进去。
private void set(ThreadLocal<?> key, Object value) // 利用hashCode寻址 Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); // 找到空位后跳出for循环 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) // 去除失效的entry,并跳出循环(因为有坑空出来了) 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; // 从当前失效的Entry往前找,直至找到空位,让slotToExpunge等于该空位后第一个失效的Entry的下标 // 原因:每次删除的时两个null值之间的失效值 int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; // 从当前失效的Entry往后找,直至找到空位 for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) ThreadLocal<?> k = e.get(); // 如果已存在key,直接替换即可 if (k == key) e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; // 因为这里做了替换,故:当起始删除下标=输入下标时,需要让起始输入下标=替换后的下标 if (slotToExpunge == staleSlot) slotToExpunge = i; // 清除失效数据 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; // 如果两个null期间还有其他失效值,让slotToExpunge等于它 if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; // 删除失效值 tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // 如果为true,说明两个null之间除了staleSlot位置,还有其他位置有失效数据,要清理 // 这也是为什么前面找到了失效值,就让slotToExpunge等于它 if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); // 用于扫描控制:如果没有遇到脏entry就整个扫描过程持续log2(n)次,log2(n)的得来是因为 n>>>=1 // 如果在扫描过程中遇到脏entry的话就会令n为当前hash表的长度(n=len),再扫描log2(n)趟 // n增加是为了增加循环次数从而通过nextIndex往后搜索的范围扩大 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 void rehash() // 先清理失效数据 expungeStaleEntries(); // 因为最开始的threshold = len*2/3,这里判断下,如果大于 3/4的threshold再扩容 // 也是为了更少的扩容操作 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); // 删除失效Entry和下一个坑之间的失效Entry,返回下一个为null的下标 private int expungeStaleEntry(int staleSlot) Entry[] tab = table; int len = tab.length; // 删除这个失效的Entry 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; // 双倍扩容,并进行copy 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; // Help the GC 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;
1.4 getEntry:先寻址,不是它就往后查找
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); //由于其set机制,get时需要往后查找 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) // 失效了,还未删除,删除后i位置为null,故返回的tab[i]为null expungeStaleEntry(i);//清理失效数据 else i = nextIndex(i, len); e = tab[i]; return null;//如果e是null的话,直接返回null,说明被清理掉了或不存在
1.5 remove方法:同样的,删除的同时清理失效数据
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;
2. ThreadLocalMap.Entry
这里Entry继承了WeakReference<ThreadLocal<?>>,作为key值的ThreadLocal被定义为弱引用,当GC时会将key值回收,而value一直存在,导致内存泄漏。
内存泄漏的两种可能:
1. 除了 弱引用 引用的实例之外,其他实例已被回收,gc后该实例也被回收,key为null,value一直数组持有无法回收
2. 使用static的ThreadLocal的引用是强引用,该强引用作为key放进ThreadLocalMap,如果不手动删除,该ThreadLocal对象不会被回收(除非线程结束,线程中的ThreadLocalMap也随之消失)
ThreadLocalMap中的解决内存泄漏的办法:调用 ThreadLocal 内部提供的 set、get 方法时,内部调用内部类 ThreadLocalMap 的 get、set 方法,清除为 key 为 null 的 value
强引用:绝对不回收
软引用:内存不足时回收
弱引用:下一次GC时回收
虚引用:对象的回收不受虚引用影响(可以认为没有引用),唯一的作用就是当对象回收时会收到系统通知
static class Entry extends WeakReference<ThreadLocal<?>> /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) super(k); value = v;
3. Thread类:Thread类的方法都是通过获取当前线程,拿到线程私有的ThreadLocalMap进行操作的
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 T get() Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) @SuppressWarnings("unchecked") T result = (T)e.value; return result; return setInitialValue(); public void remove() ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this);
以上是关于ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章