Home  >  Article  >  Java  >  Analysis and introduction to the implementation principle of ThreadLocal (with code)

Analysis and introduction to the implementation principle of ThreadLocal (with code)

不言
不言forward
2019-02-16 13:37:472928browse

The content of this article is an analysis and introduction to the implementation principle of ThreadLocal (with code). It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

ThreadLocal, a thread local variable, is used to maintain an independent copy of the variable for each thread that uses it. This variable is only valid during the life cycle of the thread. And unlike the lock mechanism, which trades time for space, ThreadLocal does not have any lock mechanism. It trades space for time to ensure the thread safety of variables.

This article analyzes the implementation principle of ThreadLocal from the source code.

Let’s take a look at the ThreadLocal class diagram structure first

 

SuppliedThreadLocal is mainly used by JDK1.8 to extend support for Lambda expressions. If you are interested, please refer to Baidu.

ThreadLocalMap is the static inner class of ThreadLocal and the class that actually saves variables.

Entry is the static inner class of ThreadLocalMap. ThreadLocalMap holds an Entry array, with ThreadLocal as the key and the variable as the value, encapsulating an Entry.

The following is a diagram briefly explaining the relationship between Thread, ThreadLocal, ThreadLocalMap and Entry.

 

Explain the picture above:

  1. A Thread owns a ThreadLocalMap object

  2. ThreadLocalMap owns an Entry array

  3. Each Entry has k--v

  4. The key of Entry is a specific ThreadLocal object

The main methods are analyzed below.

1.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);
    }

It can be seen here: a Thread only has one ThreadLocalMap object; the specific stored value is called set() of ThreadLocalMap, and the parameter key passed in is the current ThreadLocal object.

Look again at the set() method of ThreadLocalMap:

private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1); // 1

            for (Entry e = tab[i];  // 2
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

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

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

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

  1. Calculate the array index by taking the modulo of the hashCode of the key and the array capacity -1

  2. Traverse from the current index and clear invalid entries with null keys

  3. Encapsulate K-V as Entry and put it into the array

  4. Determine whether Entry is required Array expansion. The value of threshold is 2/3 of the array capacity.

Take a look at the resize() method of expansion:

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

The main thing here is to expand the capacity to 2 times the original size. Then iterate through the old array and recalculate the Entry's position in the new array based on the new array capacity.

2. get()

The get() method of ThreadLocal is as follows:

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

The getEntry() method of ThreadLocalMap is as follows:

##
private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1); // 1
            Entry e = table[i];
            if (e != null && e.get() == key) // 2
                return e;
            else
                return getEntryAfterMiss(key, i, e); //3
        }

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

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

  1. Calculate index

  2. If the Entry on the current index is not empty and the key is the same, return directly

  3. Otherwise, go to the adjacent index to search

  4. . If an invalid key is found, it will be cleared. End the cycle when found.

3. remove()

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
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;
                }
            }
        }

The processing method is similar to search and save. After deleting the corresponding Entry, invalid elements with null keys will be removed.

Note##

static class Entry extends WeakReference<ThreadLocal<?>> {}

ThreadLocal may have OOM issues. Because ThreadLocalMap uses the weak reference of ThreadLocal as the key, when GC occurs, the key is recycled, so we cannot access the value element with a null key. If the value itself is a larger object, then if the thread does not end, the value will be It has never been able to be recycled. Especially when we use the thread pool, threads are reused and threads will not be killed. In this way, when the ThreadLocal weak reference is recycled, the value will not be recycled.

When using ThreadLocal, the ThreadLocal.remove() method must be explicitly called when the thread logic code ends.

The above is the detailed content of Analysis and introduction to the implementation principle of ThreadLocal (with code). For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:cnblogs.com. If there is any infringement, please contact admin@php.cn delete