本文最后更新于 1176 天前,其中的信息可能已经有所发展或是发生改变。
ThreadLocal结构图
- 结构变化后带来的好处
- 早期ThreadLocalMap里的Entry较多,每个线程都有一个,占用空间大
- 线程销毁时,后期的ThreadLocalMap将会销毁,释放资源
- 设置默认值,也就是当前线程和ThreadLocal对象还未往ThreadLocalMap里存数据时
ThreadLocal<String> threadLocal2 = new ThreadLocal<String>(){ @Override protected String initialValue() { return "hello"; } }; System.out.println(threadLocal2.get());
- 因重写了ThreadLocal里的方法,该对象实际上是生成的一个匿名类,并继承了ThreadLocal
-
我们看看ThreadLocal如何存数据的
- 获取当前线程的ThreadLocalMap
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
- ThreadLocalMap的set方法
private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i];//遍历当前线程的ThreadLocalMap e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) {//发现key,则覆盖value e.value = value; return; } if (k == null) {//发现某个key对应的ThreadLocal对象已被回收 replaceStaleEntry(key, value, i);//详见下方解析 return; } } tab[i] = new Entry(key, value);//没有找到key,说明是第一次存这个key int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash();
- new Entry
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k);//调用WeakReference的构造方法,使ThreadLocalMap里的key与ThreadLocal对象保持弱引用的关系,即不影响该对象的gc value = v; } }
- 获取当前线程的ThreadLocalMap
- 上面的代码中,如果发现
ThreadLocalMap
里有key为null,会执行replaceStaleEntry(key, value, i);
该方法里调用了expungeStaleEntry(int staleSlot)
private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null;//清理该key对应的value tab[staleSlot] = null;//清理该key size--; // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len);//遍历ThreadLocalMap (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) {//清理key为null的Entry e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1);//重新计算hash if (h != i) {//如果不是当前位置,将移动 tab[i] = null;//置空当前位置的Entry // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null)//如果重新计算哈希得出的位置有值了,就往后找,直到找到个不为空的 h = nextIndex(h, len); tab[h] = e; } } } return i; }
- 上诉方法是可以防止内存泄露,将为null的key对应的Entry清理。
-
ThreadLocalMap使用的是线性探查法,与此相关的算法有
HashMap
的拉链法 -
回收过程
- ThreadLocal引用销毁(这个词可能不专业)
- ThreadLocal对象因没有强引用,只有个弱引用,gc时将回收。这就是为什么用弱引用,使用强引用时,会影响ThreadLocal对象的回收
- 如果ThreadLocal对象被回收了,ThreadLocalMap里依旧存着该对象的虚引用为key的Entry(包括Value),这将会造成内存泄漏,如何避免呢?
-
实际代码中,ThreadLocalMap会在set,get以及resize等方法中对stale slots做自动删除(set以及get不保证所有过期slots会在操作中会被删除,而resize则会删除threadLocalMap中所有的过期slots)。当然将threadLocal对象设置为null并不能完全避免内存泄露对象,最安全的办法仍然是调用ThreadLocal的remove方法,来彻底避免可能的内存泄露。
-
ThreadLocal对象可设置为静态变量,这样在任何地方都能方便的获取到该对象,执行remove。
老余真滴厉害啊
嘿嘿嘿,一起学习吧!