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对象强、软、弱、虚四种引用

ThreadLocal使用及原理详解

栏目