在ThreadLocal的get(),set()的时候都会清除线程ThreadLocalMap里所有key为null的value。
而ThreadLocal的remove()方法会先将Entry中对key的弱引用断开,设置为null,然后再清除对应的key为null的value。
本文分析get方法

ThreadLocal类的get方法

public T get() { 
    Thread t = Thread.currentThread(); 
    ThreadLocalMap map = getMap(t);     // 获取线程t中的ThreadLocalMap 
    if (map != null) { 
        ThreadLocalMap.Entry e = map.getEntry(this);    // 获取entry,见代码1 
        if (e != null) { 
            @SuppressWarnings("unchecked") 
            T result = (T)e.value; 
            return result; 
        } 
    } 
    return setInitialValue();   // 没有找到对应的值,调用setInitialValue方法并返回初始值,见代码4 
}

关键逻辑就是去当前线程的ThreadLocalMap中获取对应此ThreadLocal对象的entry,如果获取到了就返回entry的value。否则返回调用setInitialValue方法的结果。

代码1
ThreadLocal.ThreadLocalMap类的getEntry方法

private Entry getEntry(ThreadLocal<?> key) { 
    int i = key.threadLocalHashCode & (table.length - 1); 
    Entry e = table[i]; 
    if (e != null && e.get() == key)    // 在key计算hash的位置上直接命中查询,直接返回该entry 
        return e; 
    else 
        return getEntryAfterMiss(key, i, e);        // 没有直接命中,调用getEntryAfterMiss,见代码2 
}

如果在key计算hash的位置上直接命中查询,直接返回该entry,否则调用getEntryAfterMiss并返回结果。

代码2
ThreadLocal.ThreadLocalMap类的getEntryAfterMiss方法

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { 
    Entry[] tab = table; 
    int len = tab.length; 
 
    while (e != null) {     // 从i位置开始遍历,寻找key能对应上的entry 
        ThreadLocal<?> k = e.get(); 
        if (k == key) 
            return e; 
        if (k == null) 
            expungeStaleEntry(i);       // 遇到key为null的entry,调用expungeStaleEntry方法,见代码3 
        else 
            i = nextIndex(i, len); 
        e = tab[i]; 
    } 
    return null;        // 实在没有找到,只能返回null了 
}

在从第i个entry向后遍历的过程中,找到对应的key的entry就直接返回,如果遇到key为null的entry,则调用expungeStaleEntry方法进行清理。

代码3
ThreadLocal.ThreadLocalMap类的expungeStaleEntry方法

private int expungeStaleEntry(int staleSlot) { 
    Entry[] tab = table; 
    int len = tab.length; 
 
    // expunge entry at staleSlot 
    tab[staleSlot].value = null; 
    tab[staleSlot] = null; 
    size--;     // 以上代码,将entry的value赋值为null,这样方便GC时将真正value占用的内存给释放出来;将entry赋值为null,size减1,这样这个slot就又可以重新存放新的entry了 
 
    // Rehash until we encounter null 
    Entry e; 
    int i; 
    for (i = nextIndex(staleSlot, len); // 从staleSlot后一个index开始向后遍历,直到遇到为null的entry 
         (e = tab[i]) != null; 
         i = nextIndex(i, len)) { 
        ThreadLocal<?> k = e.get(); 
        if (k == null) {    // 如果entry的key为null,则清除掉该entry 
            e.value = null; 
            tab[i] = null; 
            size--; 
        } else { 
            int h = k.threadLocalHashCode & (len - 1); 
            if (h != i) {   // key的hash值不等于目前的index,说明该entry是因为有哈希冲突导致向后移动到当前index位置的 
                tab[i] = null; 
 
                // Unlike Knuth 6.4 Algorithm R, we must scan until 
                // null because multiple entries could have been stale. 
                while (tab[h] != null)      // 对该entry,重新进行hash并解决冲突 
                    h = nextIndex(h, len); 
                tab[h] = e; 
            } 
        } 
    } 
    return i;   // 返回经过整理后的,位于staleSlot位置后的第一个为null的entry的index值 
}

expungeStaleEntry方法不止清理了staleSlot位置上的entry,还把staleSlot之后的key为null的entry都清理了,并且顺带将一些有哈希冲突的entry给填充回可用的index中。

代码4
ThreadLocal类的setInitialValue方法

private T setInitialValue() { 
    T value = initialValue();   // initialValue()方法直接返回null 
    Thread t = Thread.currentThread(); 
    ThreadLocalMap map = getMap(t); 
    if (map != null) 
        map.set(this, value);   // 调用ThreadLocalMap的set方法 
    else 
        createMap(t, value);    // 创建新的ThreadLocalMap,并将value添加进去 
    return value; 
}

setInitialValue方法里面,真正有难度的就是在map不为null时要去调用set方法了。这种情况会在key(也就是ThreadLocal对象)对应的entry已经被清理过后出现,也有可能是一个没有设置过值的ThreadLocal对象来调用get方法,就会进入到这层逻辑。关于ThreadLocalMap的set方法,在另一篇笔记ThreadLocal源码分析:(一)set(T value)方法中有分析过了,这里就不再贴了。

ThreadLocalget方法,也可能会触发ThreadLocalMap的清理方法,将ThreadLocalMap中key为null的entry给清理掉,方便GC来回收内存。

发布评论

分享到:

IT源码网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

ThreadLocal源码分析:(一)set(T value)方法讲解
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。