ThreadLocalMap detailed explanation and source code interpretation

Overview

ThreadLocalMap is an internal class of ThreadLocal, a key-value data structure, and the core of ThreadLocal.

The data in ThreadLocalMap is stored in the table of Entry type array. Entry inherits WeakReference (weak reference). Note that the key is a weak reference, but not vlaue.

Source code interpretation

1. Member variables

	/**
	 * 初始容量
	 */
	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 is a magic number, which can reduce hash conflicts. It will be converted into binary data through the nextHashCode.getAndAdd(HASH_INCREMENT) method. The main function is to increase the hash value and reduce hash conflicts.

3. Constructor

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);
        }
    }

The set method first calculates the storage location according to the key.
If the calculated subscript is not empty, it will enter the loop. If the key is the same in the loop, it will be replaced directly. If the key is recycled, the replaceStaleEntry method will be called to clear, and the value will be set in this method .
If the calculated subscript is empty, set the value directly, and finally clear the expired key through cleanSomeSlots and determine whether to expand the capacity through 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 is based on ThreadLocal to obtain a value in ThreadLocalMap, if there is a hash conflict, linear detection through the getEntryAfterMiss method

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;
            }
        }
    }

to sum up

1. ThreadLocalMap.Entry inherits WeakReference, implements weak references, and improves the efficiency of garbage collection.
2. ThreadLocalMap may have a memory leak, because after the key is recycled, but the value still has a strong reference relationship with the Entry, it is a good habit to remove after use to avoid memory leaks.

Four references to java objects: strong, soft, weak, and virtual

Detailed explanation of the use and principle of ThreadLocal

Guess you like

Origin blog.csdn.net/qq_38306425/article/details/109247414