Java ThreadLocal源码解析: ThreadLocalMap

ThreadLocalMap在比其中Thread和ThreadLocal部分要复杂很多,是ThreadLocal底层存储和核心数据结构。从整体上将,ThreadLocalMap底层是Entry数组,key值为ThreadLocal的hash code, 采用线性探测法解决哈希冲突。

以下是ThreadLocalMap核心属性和方法,所有方法和属性都标识为private,仅为ThreadLocal可以访问:
在这里插入图片描述

ThreadLocalMap核心属性分析,主要包括底层存储数据结构,相关阈值计算,但负载因子计算貌似有点怪:

   /**
     * Entry[] table数组的初始大小为16,这个数值必须为2的幂次方
     */
    private static final int INITIAL_CAPACITY = 16;

    /**
     * 最终落地存储的数据结构是数组,其中Entry是个内部类,可以理解为key-value结构,
     * 这个数组的长度必须是2的幂
     */
    private Entry[] table;

    /**
     * 数组中实际占用了的个数
     */
    private int size = 0;

    /**
     * 阈值,达到这个阈值,将对数组进行扩容,因此需要进行rehash
     */
    private int threshold; // Default to 0

    /**
     * 这个方法就是设置threshold,是总长度的2/3
     */
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
    
    // 这些都是用来解决冲突的,说白了如果i这个位置被占了,获取i+1位置
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }

以下为ThreadLocalMap的构造函数,分别是通过ThreadLocal和value值,和通过已有ThreadLocalMap构造:

        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {    // 实际上就是key value对
            table = new Entry[INITIAL_CAPACITY];   // 以初始化大小创建Entry数组
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);  // 生成hash值,这个值为啥要选这个值?
            table[i] = new Entry(firstKey, firstValue);   // 设置找到的位置的值
            size = 1;  // 大小为1
            setThreshold(INITIAL_CAPACITY);   // 更新扩容阈值
        }

        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {          // 如果存在值
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();    // 获取ThreadLocal
                    if (key != null) {      // 如果为空了就应该被回收掉
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);   // 获取hash值
                        while (table[h] != null)    // 如果这个位置已经被占用了,访问下一个位置,直到不冲突,最终的效果是循环找
                            h = nextIndex(h, len);   // 当然是解决冲突
                        table[h] = c;   
                        size++;
                    }
                }
            }
        }

以下是ThreadLocalMap中核心操作方法,包括get,remove,rehash等基础实现:

    private Entry getEntry(ThreadLocal<?> key) {    // 根据ThreadLocal获取value值
        int i = key.threadLocalHashCode & (table.length - 1);  // 获取访问index
        Entry e = table[i];
        if (e != null && e.get() == key)  // 如果找到,并且验证
            return e;
        else   // 否者执行反向的hash的过程,其实就是从这个位置一个一个往下找
            return getEntryAfterMiss(key, i, e);
    }

    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {   // 循环从i位置往下找
        Entry[] tab = table;
        int len = tab.length;

        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;
    }
    
   private int expungeStaleEntry(int staleSlot) {  // 擦除失效的节点
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;   // staleSlot这个位置失效了,置为空,size减一
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null 
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);   // 后面的逻辑就是从这个位置开始作rehash, 因为这个空了,如果后面有有效的,拿上来填上
                 (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;
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

    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) {     // 如果当前这个ThreadLocal已经有了,将新value替换之前的
                    e.value = value;
                    return;
                }

                if (k == null) {    // 如果找到了一个key为空的位置,这个位置失效了,替换掉这个失效的位置
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)   // 
                rehash();  // 大于threshold的0.75就重新hash
        }
        
        private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
     private void rehash() {
        expungeStaleEntries();

        if (size >= threshold - threshold / 4)    // 这里有个数组的总长度,阈值,rehash的阈值,大概是总长度的2/3*0.75时才rehash
            resize();
    }

    /**
     * Double the capacity of the table.
     */
    private void resize() {
        Entry[] oldTab = table;
        int oldLen = oldTab.length;
        int newLen = oldLen * 2;  // 扩容2倍
        Entry[] newTab = new Entry[newLen];
        int count = 0;

        for (int j = 0; j < oldLen; ++j) {   // 重新hash
            Entry e = oldTab[j];
            if (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null; // Help the GC,失效的值
                } else {
                    int h = k.threadLocalHashCode & (newLen - 1);
                    while (newTab[h] != null)    // 重新找到合适的位置
                        h = nextIndex(h, newLen);
                    newTab[h] = e;
                    count++;
                }
            }
        }

        setThreshold(newLen);
        size = count;
        table = newTab;
    }    

总结:ThreadLocalMap的实现也不是很复杂,底层为一个数组,通过ThreadLocal的threadLocalHashCode作哈希,如果发现有实效的,触发清除失效值,达到阈值触发rehash,使用线性探测法定位hash位置,即通过hash值获取数组中的一个index位置,如果已被占用就去下一个位置,如果发现失效的,触发清除失效值。但是这里的负载因子有点怪,看起来需要经过两次计算,是2/3*0.75,及0.5

猜你喜欢

转载自blog.csdn.net/qq_36101933/article/details/82910090