Java中的ThreadLocalMap源码解读

 更新时间:2023年09月26日 10:30:00   作者:回家放羊吧  
这篇文章主要介绍了Java中的ThreadLocalMap源码解读,ThreadLocalMap是ThreadLocal的内部类,是一个key-value数据形式结构,也是ThreadLocal的核心,需要的朋友可以参考下

概述

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中的ThreadLocalMap源码解读的文章就介绍到这了,更多相关ThreadLocalMap源码解读内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论