ThreadLocal difficulty parsing

General introduction (memory leaks, basic usage, application scenarios, best practices, etc.) and other blog's official website said very clearly, this is mainly the record about the place and I think the difficulty of the core threadlocal.

It will mainly include the following: 1 memory leak problem, select the object reference relationship 2. threadLocalHashCode value of 3. In-depth inquiry set method.

Note: This code selected jdk8.

In fact, these two issues, as well as other issues I think are the object reference relationship, this is the core.

1. memory leaks, object reference relationship

This figure quoted from (https://mp.weixin.qq.com/s/bxIkMaCQ0PriZtSWT8wrXw##) Architect Collection practice this public number.

The figure has been quoted talking about the relationship very clear, because each thread of his own holdings is on quoted ThreadLocal thread private stack inside, and that once this thread is missing a reference to his own ThreadLocal after, if gc, then the key corresponding ThreadLocalMap entry inside the thread inside weakReference of ThreadLocal will be recovered, this value will be the value becomes unreachable state, if the entry is not timely remove out, it will causes a memory leak problem.

2. threadLocalHashCode selected value

Let us talk about this is to see what the code:

    /**
     * Get the entry associated with key.  This method
     * itself handles only the fast path: a direct hit of existing
     * key. It otherwise relays to getEntryAfterMiss.  This is
     * designed to maximize performance for direct hits, in part
     * by making this method readily inlinable.
     *
     * @param  key the thread local object
     * @return the entry associated with key, or null if no such
     */
    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }

In fact, each threadlocal corresponding hashCode! Look at his generation principle:

private final int threadLocalHashCode = nextHashCode();

carry on

/**
 * 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);
}

Is the hash value of each threadlocal is i * HASH_INCREMENT + HASH_INCREMENT;

i is the i-th reference this threadLocalHashCode thread.

Note that this is the final modified threadLocalHashCode value, each thread will have a chance to threadlocal assignment.

This magic number is a number, because each time expansion is an integer power of 2, hash value generated by this algorithm can be an array of small squares each fall uniformly inside, that is to say if the full normal process was added, was not conflict can be achieved to maximize space efficiency and reduce hash conflicts. (Of course, there will be impossible to fill the time, threadlocalMap before being filled will be expansion)

(https://mp.weixin.qq.com/s/bxIkMaCQ0PriZtSWT8wrXw##)

3. In-depth inquiry set method.

We look at the core of the code, set method, before the first look at a simple get, will finally get the call set.

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

getMaps here first I thought it was the t value as the key to map inside the investigation, not actually, is the return threadlocalmap current thread holds.

    /**
     * Get the entry associated with key.  This method
     * itself handles only the fast path: a direct hit of existing
     * key. It otherwise relays to getEntryAfterMiss.  This is
     * designed to maximize performance for direct hits, in part
     * by making this method readily inlinable.
     *
     * @param  key the thread local object
     * @return the entry associated with key, or null if no such
     */
    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }

get here is directly calculated hashcode and len-1 and then go find the Chinese entry value, getEntryAfterMiss we do not look at this branch. We look at the above setInitialValue.

/**
 * Variant of set() to establish initialValue. Used instead
 * of set() in case user has overridden the set() method.
 *
 * @return the initial value
 */
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;
}

Here's a small place, it is to initialize method is triggered when the first call to get in.
Then we went to see the inner class ThreadLocalMap.set TheadLocal.

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
    private void set(ThreadLocal<?> key, Object value) {

        // We don't use a fast path as with get() because it is at
        // least as common to use set() to create new entries as
        // it is to replace existing ones, in which case, a fast
        // path would fail more often than not.

        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) {
                e.value = value;
                return;
            }
            //走到以上都是常规逻辑,走到这里就有点奇怪了。
            /*其实这里就是为了解决内存泄漏问题的,当走到k==null且entry非空的地方就意味着,弱引用被gc了。因此这个staleentry需要被替换掉。*/
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }

        tab[i] = new Entry(key, value);
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }

Continue to look replaceStaleEntry

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

        int slotToExpunge = staleSlot;
        //循环往前找需要被替换掉的staleslot,直到空为止,然后将最前面的i值赋值给slotToExpunge,表不可能满,肯定不能走完一个循环,下面的同理
        for (int i = prevIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = prevIndex(i, len))
            if (e.get() == null)
                slotToExpunge = i;
        
        //循环往后遍历这个数组
        for (int i = nextIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();

            if (k == key) {
                e.value = value;
                
                //找到相同的key之后,交换掉i和staleSlot,之后i就是坏节点,是需要被擦除的                
                tab[i] = tab[staleSlot];
                tab[staleSlot] = e;
                
                /*如果只有staleSlot前面没有节点,那就把i擦除掉,为什么要这样,可能是因为staleSlot是冲突之后偏移更加短的值,会更加接近真实hash值*/

                if (slotToExpunge == staleSlot)
                    slotToExpunge = i;
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                return;
            }

            /*如果这个k为空的话把这个节点变成坏节点,为什么要这样,因为staleSlot要么会拿来交换,要么就会拿来建新值,终究会变成好人,但是这个铁定是坑爹的,如果交换了之后,在下面的cleanSomeSlots之后不久交换后的坑爹也会被删掉*/
            if (k == null && slotToExpunge == staleSlot)
                slotToExpunge = i;
        }

        // 如果找不到相同的key,那就直接赋值
        tab[staleSlot].value = null;
        tab[staleSlot] = new Entry(key, value);

        // 如果这两个值不等才去删除,因为这样就找到了坑爹货,staleSlot到了这一步都是好人
        if (slotToExpunge != staleSlot)
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
    }

看expungeStaleEntry

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

        // 能进来的这个方法的staleSlot都铁定是坑爹的。
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        size--;

        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--;
        //一直对当前的entry里面的key进行修正位置,(有可能之前的线性探测之后,位置偏移过多,优化表),这里总会遇到空的, 因为表不可能满,会扩容。
            } else {
                int h = k.threadLocalHashCode & (len - 1);
                if (h != i) {
                    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)
                        h = nextIndex(h, len);
                    tab[h] = e;
                }
            }
        }
        return i;
    }

Finally, we look at cleanSomeSlot

    private boolean cleanSomeSlots(int i, int n) {
        boolean removed = false;
        Entry[] tab = table;
        int len = tab.length;
        do {
            //下面的代码是清除操作e.get获取的是key值,也就是threadlocal
            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;
    }

This method is not a good look at the beginning to understand why this cycle times: (n >>> = 1) = 0!. In fact code comments also said, this is a tradeoff, can be understood as expected. If you encounter a null value, it may be understood as a memory leak in serious condition, and just gc too, and that is expected to continue to continue to increase clearance.

Guess you like

Origin www.cnblogs.com/kobebyrant/p/11229626.html