Source-depth understanding of ThreadLocal and ThreadLocalMap

 

A .ThreadLoacl understanding:

The official said:

ThreadLocal thread is a local copy of the variable tools, mainly for the private copy of the object stored in the thread and the thread to do a mapping, variable between the various threads interfere with each other

Popular talk:

ThreadLocal also called thread-local variables, ThreadLoacl variables in each thread creates a copy of each thread can access their own internal copy of the variables simultaneously and threads

 

.TreadLocal two principles:

From the graph we can get a glimpse of the initial core mechanism of ThreadLocal:

1) Internal Thread each thread has a Map

2) Map inside thread local storage variable copy key objects and threads value

3) Thread the interior of the Map is maintained by the ThreadLocal, ThreadLocal by the thread responsible for getting and setting the values ​​of the variables to Map

So for different threads, each time you get a copy of the value, other threads can not get to a copy of the current thread's value, thus forming a copy of isolation, without disturbing each other

 

Three underlying source .ThreadLocal

ThreadLocal class provides the following core methods:

1.get method: Get a copy of the current thread variable values

2.set: Set the current thread's copy of the variable value

3.remove method: Removes the current thread's copy of the variable value

4.initilaValue method: initialize the current thread's copy of the variable value, null initialization

1)ThreadLocal.get():

 

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

protected T initialValue() {
    return null;
}

 

Source Analysis:

1. Obtain ThreadLocalMap objects threadLocals current thread (save a copy of the actual value of the Map)

2.Map not empty, then, obtain the storage node thread Entry KV from the Map, and acquires a copy of the value from the Value Entry node returns

3.Map is empty, then return null initial value, then need to add value to the Map as null key-value pairs, to avoid NullPointerException

 

2.ThreadLocal.set():

 

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

 

Source Analysis:

1. Obtain the member variables for the current thread Map

2.Map not empty: Value re ThreadLocal objects and a copy placed in the Map

3.Map empty: ThreadLocalMap thread member variables are initialized to create and copy ThreadLocal objects and Value into the Map

 

3.ThreadLocal.remove():

 

public void remove() {
 ThreadLocalMap m = getMap(Thread.currentThread());
 if (m != null)
     m.remove(this);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

Copy the code

Source analysis:

ThreadLocalMap directly call the remove method (Later we will explore the underlying source code ThreadLocalMap class!, Stood here first)

 

4.ThreadLocal.initialValue() :

protected T initialValue() {
    return null;
}

It is a direct return null

 

Summary about: We found that the underlying source code has a ThreadLocal ThreadLocalMap class, the class of the underlying source code ThreadLocalMap what is it? We together look!

 

Four .ThreadLocalMap underlying source code analysis

ThreadLocalMap Map implementation is a ThreadLocal inside, but it does not implement any of a set of interface specifications, as it is for internal use ThreadLocal, using an array of data structures + prescribing address method, Entry inheritance WeakRefrence, to achieve this particular scenario based ThreadLocal Map, its implementation is worth taking research! !

 

1.ThreadLocalMap source of Entry

Copy the code

static class Entry extends WeakReference<ThreadLocal> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

Copy the code

Source analysis:

1.Entry the key can only be ThreadLocal object is defined dead

2.Entry inherited WeakRefrence (weak references, can only live the life cycle before the next GC), but Key is only weak references, Value is not weak references

ps: value since it is not weak references, then the key (key = null) Value has not been recovered after being recovered, recycled if the current thread is that okay, this value can also be recycled and threads together, and if the current thread is the thread pool such an environment, the end of the thread is not to destroy the recovery, then the Value never be recovered, so when there is a lot of value, it will have a memory leak, then how Java 8 to resolve this issue?

Solution:

Write pictures described here

These are the set of methods ThreadLocalMap, for loop through the array Entry, met key = null will be replaced, so there is no question of the value of memory leaks! ! !

 

Computing the key in HashCode 2.ThreaLocalMap

ThreaLocalMap the key is ThreaLocal, it does not call the traditional ThreadLocal hashcode method (inherited from the object's hashcode), but calls nexthashcode, source code as follows:

Copy the code

private final int threadLocalHashCode = nextHashCode();

 private static AtomicInteger nextHashCode = new AtomicInteger();

 //1640531527 这是一个神奇的数字,能够让hash槽位分布相当均匀
 private static final int HASH_INCREMENT = 0x61c88647; 

 private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
 }

Copy the code

源码分析:

我们发现ThreaLocalMap的hashcode计算没有采用模长度的方法,没有采用拉链法,采用的是开放地址法,其槽位采用静态的AtomicInteger每次增加1640531527实现,冲突了则加1或者减1继续进行增加1640531527

我们把这个数叫做魔数,通过这个魔数我们可以位key产生完美的槽位分配,hahs冲突的次数很少

(据说魔数和黄金比例,斐波那契数列存在某种关系)

 

3.ThreaLocalMap中set方法:

Copy the code

private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1); // 用key的hashCode计算槽位
    // hash冲突时,使用开放地址法
    // 因为独特和hash算法,导致hash冲突很少,一般不会走进这个for循环
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) { // key 相同,则覆盖value
            e.value = value; 
            return;
        }

        if (k == null) { // key = null,说明 key 已经被回收了,进入替换方法
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // 新增 Entry
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold) // 清除一些过期的值,并判断是否需要扩容
        rehash(); // 扩容
}

Copy the code

源码分析:

1.先是计算槽位

2.Entry数组中存在需要插入的key,直接替换即可,存在key=null,也是替换(可以避免value内存泄漏)

3.Entry数组中不存在需要插入的key,也没有key=null,新增一个Entry,然后判断一下需不需要扩容和清除过期的值(关于扩容和清除过期值先不细讲)

 

4.ThreadLocalMap中getEntry方法:

Copy the code

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key) // 无hash冲突情况
        return e;
    else
        return getEntryAfterMiss(key, i, e); // 有hash冲突情况
}

Copy the code

源码分析:

1.计算槽位i,判断table[i]是否有目标key,没有(hahs冲突了)则进入getEntryAfterMiss方法

getEntryAfterMiss方法分析:

Copy the code

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i); // 清除过期的slot
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

Copy the code

源码分析:

遇到hash冲突之后继续向后查找,并且会在查找路上清除过期的slot

 

5.ThreadLocalMap中rehash方法:

Copy the code

private void rehash() {
    expungeStaleEntries();

   // 清除过程中,size会减小,在此处重新计算是否需要扩容
   // 并没有直接使用threshold,而是用较低的threshold (约 threshold 的 3/4)提前触发resize
    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);
    }
}

Copy the code

源码分析:

先调用expungeStaleEntries()清除所有过期的slot,然后提前触发resize(约 threshold 的 3/4的时候)

下面看看resize():

Copy the code

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) {
                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;
}

Copy the code

扩容2倍,同时在Entry移动过程中会清除一些过期的entry

 

6.ThreadLocal中的remove方法:

Copy the code

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;
        }
    }
}

Copy the code

源码分析:

遍历Entry数组寻找需要删除的ThreadLocal,建议在ThreadLocal使用完成之后再调用此方法

 

现在再详细分析一下ThreadLocalMap的set方法中的几个方法:

1.replaceStaleEntry方法:替换

Copy the code

private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                               int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    Entry e;

    // 往前寻找过期的slot
    int slotToExpunge = staleSlot;
    for (int i = prevIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = prevIndex(i, len))
        if (e.get() == null)
            slotToExpunge = i;

    // 找到 key 或者 直到 遇到null 的slot 才终止循环
    for (int i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();

        // 如果找到了key,那么需要将它与过期的 slot 交换来维护哈希表的顺序。
        // 然后可以将新过期的 slot 或其上面遇到的任何其他过期的 slot 
        // 给 expungeStaleEntry 以清除或 rehash 这个 run 中的所有其他entries。

        if (k == key) {
            e.value = value;

            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;

            // 如果存在,则开始清除前面过期的entry
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }

        // 如果我们没有在向前扫描中找到过期的条目,
        // 那么在扫描 key 时看到的第一个过期 entry 是仍然存在于 run 中的条目。
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }

    // 如果没有找到 key,那么在 slot 中创建新entry
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);

    // 如果还有其他过期的entries存在 run 中,则清除他们
    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

Copy the code

上文中run的意思不好翻译,理解为开放地址中一个slot中前后不为null的连续entry

 

2.cleanSomeSlots方法:清除一些slot(按照规则清除“一些”slot,而不是全部)

Copy the code

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);  // n = n / 2, 对数控制循环 
    return removed;
}

Copy the code

当新元素被添加时,或者另外一个过期元素已经被删除的时候,会调用该方法,该方法会试探性的扫描一些Entry寻找过期的条目,它执行对数数量的扫描,是一种基于不扫描(快速但保留垃圾)和所有元素扫描之间的平衡!!

对数数量的扫描!!!

这是一种折中的方案

 

3.expungeStaleEntry:清除

Copy the code

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // 清除当前过期的slot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // Rehash 直到 null 的 slot
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (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;
}

 

The real clear, will not only clear the current outdated slot, will continue until the next inquiry until it encounters a null slot, in the case of query traversal has not been recovered, made a rehash

 

Recommended Gangster blog: https://www.cnblogs.com/micrari/p/6790229.html

Written too detailed, too strong, multi-source comments thief

Published 17 original articles · won praise 2 · views 90000 +

Guess you like

Origin blog.csdn.net/u011250186/article/details/104441203