ThreadLocal源码分析

源码比较简单,但是里面有很多优化的地方。

TheadLocal.get()的时候实际上拿到当前线程的threadLocalMap,以theadLocal为key,取value的过程。

源码为证:

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
但是ThreadLocalMap是一个静态内部类。其中的entry是一个weakReference,其中ThreadLocal是key。所以当GC发生时,无论是minor GC还是major GC,如果threadLocal没有外部强引用,那么threadlocal会被GC掉,但是value是entry的强引用,不会被GC,这样的entry被称为stale Entry。这样存在的一个问题就是在适当的时机从threadlocalmap中删除entry。

来看看ThreadLocal是怎么处理的,判断是否是statle Entry的代码很简单,就是entry.get()是否为空:

private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            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;

                        // 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;
        }
可以看到会把从staleSlot到null entry这一段内所有的stale entry都删除掉,并且非stale的entry会重新rehash。保证stale slot所在的run(指两个null entry之间的所有entry)内不再有stale slot。但不是删除map内所有的stale entry。

另外一个删除stale entry的方法是:

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;
        }
可以看到内部调用了expungeStaleEntry,但是也有漏掉statle entry的情况,比如说在从i开始的log2(n)都不存在stale entry,但是其他地方存在stale的场景。

在get或者set中会调用上面的两个方法来删除部分stale,但是有可能不是全部。

所以存在内存泄露,记的把TheadLocal设置为static,然后调用remove方法。

你可能感兴趣的:(memory,threadlocal,源码)