JAVA源码分析-ThreadLocal

我们对ThreadLocal的两个主要公共方法set和get方法进行分析:

一、首先看下set方法:

  public void set(T value) {

        Thread t = Thread.currentThread();
//根据当前线程获取ThreadLocal的内部类ThreadLocalMap实例
        ThreadLocalMap map = getMap(t);
        if (map != null)
    //调用ThreadLocalMap的set方法,由此看见值的存储是由ThreadLocalMap完成的
            map.set(this, value);
        else
            createMap(t, value);
    }


   由上可知:实际核心操作是调用了ThreadLocalMap.set()方法:
     private void set(ThreadLocal key, Object value) {
    //Entry是一个继承了WeakReference弱引用的内部类
            Entry[] tab = table;
            int len = tab.length;
    //根据ThreadLocal的hasCode进行一次散列计算得到当前key应该存储的位置
            int i = key.threadLocalHashCode & (len-1);    
    //遍历,每次读取下一个位置的元素
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();
//遇到一致的ThreadLocal,则覆盖旧值并返回
                if (k == key) {
                    e.value = value;
                    return;
                }
//遇到为null的ThreadLocal,则清理陈旧的元素并将新值正确存储
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
    //如果上面的遍历过程中没有遇到一致的ThreadLocal并且也没有遇到为null的ThreadLocal
    //则新建一个Entry元素,并存入table数组中
            tab[i] = new Entry(key, value);
            //数组大小自增1
    int sz = ++size;
    //判定是否需要重新整理数组
    //rehash方法会清理数组中无用的元素,然后判定是否需要进行重新扩容,扩容是原来的两倍
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

   我们看下replaceStaleEntry(ThreadLocal key, Object value,int staleSlot)这个方法:
   private void replaceStaleEntry(ThreadLocal key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;
            //申明一个局部变量标记陈旧位置
            int slotToExpunge = staleSlot;
    //遍历,从staleSlot开始,每次读取上个位置的元素
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
//遇到Entry中的ThreadLocal为null,则重置标记位
                if (e.get() == null)
                    slotToExpunge = i;
            //遍历,从staleSlot开始,每次读取下个位置的元素
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal k = e.get();
                //遇到一致的ThreadLocal,则覆盖旧值,并将数组中tab[i]和tab[staleSlot]两个位置上的元素互换
                if (k == key) {
                    e.value = value;
                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;
                    //检查标记位是否修改,如果没有修改则重置标记位为i
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
    //清理标记位
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }
                //遇到Entry中的ThreadLocal为null,并且标记位自初始化之后未发生过改变,则重置标记位
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }
            tab[staleSlot].value = null;
    //如果经过上述步骤仍没有将值正确存储,则新建一个Entry元素,存入tab[staleSlot]中
            tab[staleSlot] = new Entry(key, value);
            //检查标记位是否自初始化之后未发生过改变,如果发生改变,则清理标记位
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

    接下来看下rehash()方法:
    private void rehash() {
    //对数组中所有元素进行遍历,并判断是否需要清理
            expungeStaleEntries();
            //判断当前数组大小是否高于阈值的3/4,如果高了,则进行扩容
            if (size >= threshold - threshold / 4)
                resize();
    }

    扩容方法:resize():
    private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            //新的数组容量为旧数组的两倍
    int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;
            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal k = e.get();
                    if (k == null) {
    //元素的值是与线程强关联的,只会伴随线程的结束才会被GC,所以这里将value=null,可以帮助GC
                        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;
        }

二、继续分析get方法:
    public T get() {
        Thread t = Thread.currentThread();
        //根据ThreadLocal找到对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
        if (map != null) {
    //根据ThreadLocal从ThreadLocalMap找到对应的Entry元素
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
//Entry元素中的value即为当前线程的变量副本
                return (T)e.value;
        }
        return setInitialValue();
    }


    接着看下getEntry()方法:
    private Entry getEntry(ThreadLocal key) {
    //进行一次哈希算法得到对应key的位置
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
//如果根据哈希映射得到的变量为空或key值不一致,则走该流程
                return getEntryAfterMiss(key, i, e);
        }


    接着看下getEntryAfterMiss()方法:
    private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;    
    //如果Entry不为null,则继续轮询查找key一致的元素
            while (e != null) {
                ThreadLocal k = e.get();
                if (k == key)
                    return e;
                if (k == null)
            //当轮询时发现有key==null的元素则调用清理操作
                    expungeStaleEntry(i);
                else
    //取下一个元素
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;

        }

总结:

1、分析set方法的过程中我们发现ThreadLocal中实际存储值的操作是由其内部类ThreadLocalMap实现,ThreadLocalMap使用Entry的方式存储每个线程对应的变量,ThreadLocal在其中扮演的角色是始终是引用的作用,根据ThreadLocal在ThreadLocalMap查找对应的Entry,在Entry中又作为一个弱引用存在。

2、ThreadLocal和Thread的从属关系实际是ThreadLocal是Thread的附属关系,在源码中多处可以看出这种关系,例如:get方法中,我们可知ThreadLocalMap属于Thread,根据ThreadLocal从ThreadLocalMap中获取Entry,由此可以看出ThreadLocal和Thread的从属关系。

3、ThreadLocal的内部类ThreadLocalMap的扩容条件是当前容量大于3/4阈值大小,阈值大小是初始值大小的2/3,初始值默认大小为16,根据的整除算法,所以默认阈值大小为10。

猜你喜欢

转载自blog.csdn.net/ignorewho/article/details/80451399