ThreadLocalMapの詳細な説明とソースコードの解釈(ThreadLocal)

概要概要

ThreadLocalMapは、キー値データ構造であるThreadLocalの内部クラスであり、ThreadLocalのコアです。

ThreadLocalMapのデータは、エントリタイプの配列のテーブルに格納されます。エントリはWeakReference(弱い参照)を継承します。キーは弱い参照ですが、vlaueではないことに注意してください。

javaオブジェクトへの4つの参照:strong、soft、weak、virtual

ThreadLocalの使用法と原理の詳細な説明

ソースコードの解釈

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はマジックナンバーであり、ハッシュの競合を減らすことができます。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.セット

	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メソッドは、最初にキーに従って保存場所を計算します。
計算された添え字が空でない場合はループに入ります。キーがループ内で同じである場合は直接置き換えられます。キーがリサイクルされる場合は、replaceStaleEntryメソッドが呼び出されてクリアされ、このメソッドで値が設定されます。 。
計算された添え字が空の場合は、値を直接設定し、最後にcleanSomeSlotsを使用して期限切れのキーをクリアし、再ハッシュを使用して展開するかどうかを決定します。

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.削除

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にはメモリリークがある可能性があります。これは、キーがリサイクルされた後でも、値がエントリと強い参照関係にあるため、メモリリークを回避するために、使用後に削除することをお勧めします。

おすすめ

転載: blog.csdn.net/qq_38306425/article/details/109304598