ThreadLocal of the source code series

Introduction

   The role of ThreadLocal is to isolate data . The stored variables only belong to the current thread, which is equivalent to the local variables of the current thread . In a multi-threaded environment, they will not be accessed and modified by other threads. It is often used to store thread private member variables , context , and for the same thread, passing parameters between methods at different levels, etc. ThreadLocal in JDK 1.8 has a total of 741 lines of code, including 3 member variables, 13 member methods and two inner classes. Let's first look at the core principles, and then look at the source code in detail.

question

We can study this part of the content with questions, and hope that after the study, we can answer these questions:

  1. Can ThreadLocal replace Synchronized? What is the difference between Synchronized and Synchronized?
  2. What is the relationship between Thread, ThreadLocal, and ThreadLocalMap?
  3. Stored in the jvm's heap or stack?
  4. Can ThreadLocal cause a memory leak and why?
  5. Why does ThreadLocalMap use Entry array instead of Entry object?
  6. Are objects in ThreadLocal necessarily thread-safe?
  7. Does ThreadLocalMap only use simple arrays to store values? How to save the value if there is a hash collision?

core principle

   This starts with the java.lang.Thread class. Each Thread object has a member variable of ThreadLocalMap (the inner class of ThreadLocal). ThreadLocalMap has an Entry array inside, each Entry is a key-value pair, the key is ThreadLocal itself, and the value is the generic value of ThreadLocal. (Although the Thread class has a ThreadLocalMap member variable here, it does not have get(), set(), remove() and other methods for adding, deleting, modifying and checking. It is actually operated through ThreadLocal.) For example, every time we request, it is a thread, and
   then There is one and only one ThreadLocalMap in a thread, and then several ThreadLocal objects may be new in our business, and several ThreadLocal values ​​are stored, which are stored in the Entry array, and then we can find them according to the current thread and the current ThreadLocal Unique <T>value value. To put it more simply: each thread has a member variable, which is essentially an array, which stores several objects that you want the thread to privatize .
The structure diagram is as follows:
insert image description here
the core source code is as follows:

// java.lang.Thread类里持有ThreadLocalMap的引用
public class Thread implements Runnable {
	... ...
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ... ...
}

// java.lang.ThreadLocal有内部静态类ThreadLocalMap,主要提供给Thread类使用
public class ThreadLocal<T> {
	
	... ...
	
    static class ThreadLocalMap {
    	// 存线程私有变量
        private Entry[] table;
        
        // ThreadLocalMap内部有Entry类,Entry的key是ThreadLocal本身,value是泛型值
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }

	... ...
}

Member variables of ThreadLocal

  Here is an introduction to the member variables of ThreadLocal.

  • private final int threadLocalHashCode = nextHashCode()
    The custom hash value is mainly obtained by calling the nextHashCode() method.
  • private static AtomicInteger nextHashCode = new AtomicInteger()
    The next hash code to be given. Automatic updates. Start from 0.
  • private static final int HASH_INCREMENT = 0x61c88647;
    The difference between successively generated hash codes, this is a magic number.

Member methods of ThreadLocal

  Here is an introduction to the member methods of ThreadLocal.

  • public ThreadLocal()
    constructor.
	public ThreadLocal() {
    }
  • private static int nextHashCode()
    returns the next hash code.
	private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
  • protected T initialValue()
    is used to initialize the value. This method will be called when the thread uses the get method to access the variable for the first time. If set() is used to set the value before calling get(), it will not be called. It is just simple here is initialized to null. If you need to initialize to other values, you must subclass ThreadLocal and override this method.
// 由子类提供实现。
// protected
protected T initialValue() {
   return null;
}
  • public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)
    can generate an initial value according to the provided function.
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }
  • public T get()
    returns the thread local variable value corresponding to the current thread.
public T get() {
    // 获取当前线程,这里的currentThread()是个native方法
    Thread t = Thread.currentThread();
    // 获取当前线程对应的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    // 若获取到了。则获取此ThreadLocalMap下的entry对象,若entry也获取到了,那么直接获取entry对应的value返回即可
    if (map != null) {
        // 获取此ThreadLocalMap下的entry对象,把当前ThreadLocal当参数传进去
        ThreadLocalMap.Entry e = map.getEntry(this);
        // 若entry也获取到了
        if (e != null) {
            @SuppressWarnings("unchecked")
            // 直接获取entry对应的value返回
            T result = (T)e.value;
            return result;
        }
    }
    // 若没获取到ThreadLocalMap或没获取到Entry,则设置初始值
    //  初始值方法是延迟加载
    return setInitialValue();
}
  • boolean isPresent()
    returns true if ThreadLocalMap is not empty in the current thread and the local variable is not empty, otherwise returns false.
	boolean isPresent() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        return map != null && map.getEntry(this) != null;
    }
  • private T setInitialValue()
    sets the initial value and calls initialValue.
// 设置初始值
private T setInitialValue() {
    // 调用初始值方法,由子类提供。
    T value = initialValue();
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取map
    ThreadLocalMap map = getMap(t);
    // 获取到了
    if (map != null)
        // set
        map.set(this, value);
    else
        // 没获取到。创建map并赋值
        createMap(t, value);
    // 返回初始值。
    return value;
}
  • public void set(T value)
    sets the value of the thread local variable of the current thread.
public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程对应的ThreadLocalMap实例
    ThreadLocalMap map = getMap(t);
    // 若当前线程有对应的ThreadLocalMap实例,则将当前ThreadLocal对象作为key,value做为值存到ThreadLocalMap的entry里。
    if (map != null)
        map.set(this, value);
    else
        // 若当前线程没有对应的ThreadLocalMap实例,则创建ThreadLocalMap,并将此线程与之绑定
        createMap(t, value);
}
  • public void remove()
    deletes the value of the local variable of the current thread, the purpose is to reduce memory usage and prevent memory leaks.
public void remove() {
    // 获取当前线程的ThreadLocalMap对象,并将其移除。
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}
  • ThreadLocalMap getMap(Thread t)
    gets ThreadLocalMap, which is called when you call the ThreadLocal.get() method, and its return is a reference to the threadLocals in the current thread.
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
  • void createMap(Thread t, T firstValue)
    creates ThreadLocalMap, and the bottom layer of ThreadLocal is actually a map to maintain.
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

// ThreadLocalMap构造器。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // new了一个ThreadLocalMap的内部类Entry,且将key和value传入。
    // key是ThreadLocal对象。
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
  • static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap)
    uses a factory method to create a map of inherited thread local variables.
	static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
  • T childValue(T parentValue)
    childValue() is used to define the implementation in the ThreadLocal subclass. The definition here is mainly provided to the createInheritedMap factory method call.
T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

Inner class of ThreadLocal

  Here is an introduction to the internal class of ThreadLocal.

  • static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {}
    The extension of ThreadLocal, get the initial value from the specified provider.
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

        private final Supplier<? extends T> supplier;
        
        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }

        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }
  • static class ThreadLocalMap {}
    ThreadLocalMap is a custom hash map, only suitable for maintaining thread local values. ThreadLocalMap uses a method similar to HashMap to store ThreadLocal and its corresponding generic values, but here only arrays are used instead of linked lists. How to solve the hash collision problem without using a linked list? In fact, it is very simple, look up the next index in turn, and store the value in the next null position.
	static class ThreadLocalMap {

        /**
         * ThreadLocalMap 里数组里具体存的值
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /**
            * 与当前ThreadLocal 对应的值
            */
            Object value;

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

        /**
         * 初始容量必须是2的幂次数,当前默认为16
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * ThreadLocalMap 里的Entry[] 数组,长度必须为2的幂次数
         */
        private Entry[] table;

        /**
         * 当前 Entry[] table 的长度
         */
        private int size = 0;

        /**
         * 阈值,超过后需扩容
         */
        private int threshold; // Default to 0

        /**
         * 设置阈值为长度的 三分之二
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * I对len取模
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * 获取前一个索引值
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * 构造方法,懒加载的
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        	// 根据初始容量,初始化表
            table = new Entry[INITIAL_CAPACITY];
            // 获取当前哈希值后,对len进行取模,确定索引位置
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            // 初始化长度,和阈值
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        /**
         * 从给定父映射创建新映射         
         * */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
        	// 初始化参数
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (Entry e : parentTable) {
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

        /**
         * 查找与key相关联的条目
         */
        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);
        }

        /**
         * 根据key值找不到Entry时,用以下方法找,当前i值找不到,就到i+1处找
         */
        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);
                else
                	// 当前索引 i 处找不到,就到索引 i + 1 处查找,这也是ThreadLocalMap解决哈希冲突的方法,即,当前有值,则顺位往下一个索引存
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

        /**
         * 根据ThreadLocal,存对应value值
         */
        private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            // 用key的哈希值,对len取模,以计算存储的索引位置
            int i = key.threadLocalHashCode & (len-1);

			// 这几行代码就很有意思了,前面说ThreadLocalMap是没有链表的,那么怎么解决哈希冲突问题呢
			// 就是,依次往下一位索引存,直到有空位为止
            for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

				// 如果key相等,则更新值
                if (k == key) {
                    e.value = value;
                    return;
                }

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

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

        /**
         * 删除指定key的节点
         */
        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;
                }
            }
        }

        /**
         * 替换已经不再被使用的旧值,(key为null的值       
         */
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            int slotToExpunge = staleSlot;
            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;

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

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

                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

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

        /**
         * staleSlot和下个空槽之间的所有空槽都将被检查和清除
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

			// 清楚当前槽
            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();
                // 如果发现key为空,则清除value值
                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;
        }

        /**
         * 启发式地扫描一些单位,寻找陈旧的条目
         */
        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);
            return removed;
        }

        /**
         * 清除旧条目后,长度依然3/4threshold,则扩容
         */
        private void rehash() {
            expungeStaleEntries();

            if (size >= threshold - threshold / 4)
                resize();
        }

        /**
         * 将原来的容量,扩大为两倍
         */
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (Entry e : oldTab) {
                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;
        }

        /**
         * 删除表中所有陈旧的条目
         */
        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);
            }
        }
    }

FAQ

  I believe that after reading the above source code and some questions at the beginning of the article, you already have your own answers, and then we will check the answers again.
1. Can ThreadLocal replace Synchronized? What is the difference between Synchronized and Synchronized?
  Answer: ThreadLocal definitely cannot replace Synchronized. ThreadLocal just makes variables completely private, and other threads cannot access them. In addition to solving thread conflicts, Synchronized can make variables accessed and modified by all threads.
2. What is the relationship between Thread, ThreadLocal, and ThreadLocalMap?
  Answer: The relationship is a bit confusing. The Thread class includes ThreadLocalMap member variables. ThreadLocalMap is an internal class of ThreadLocal. ThreadLocalMap has an Entry array. The Entry entity is a key-value pair, where the key is the ThreadLocal type.
3 Stored in the heap or stack of the jvm?
  Answer: Many people think that if a variable becomes private to a thread, it must exist in the virtual machine stack. In fact, it is not. Our private variable is stored in the Thread object, and the objects are all stored in the heap, so the instance of ThreadLocal And his value is stored on the heap.

4. Will ThreadLocal cause a memory leak, why?
  Answer: This should be analyzed from two aspects, that is, the key and value of ThreadLocalMap.Entry respectively: the key is directly handed over to the parent class to handle super(key), and the parent class is a weak reference, so there is no memory leak problem at all for the key. value is a strong reference. If the thread is terminated, it will be killed by GC, but sometimes the thread will not be terminated, such as the core thread in the thread pool. At this time, the reference chain becomes: , because value and Thread still Thread->ThreadLocalMap->Entry(key为null)->valueexist The link relationship is still reachable, so it will not be recycled. In this way, more and more garbage objects are generated but cannot be recycled, which may eventually lead to OOM. Of course, the solution is simple. Use the remove() method after using up private variables. Yes, it will delete all value values.

5. Why use Entry array instead of Entry object?
  Answer: In the same thread, we may need multiple thread-private variables, so we need an array.

6. Are objects in ThreadLocal necessarily thread-safe?
  Answer: Not necessarily, because the objects entered by ThreadLocal.set() may themselves be accessible by multiple threads, such as static objects, so there is no way to preserve thread safety.

7. Does ThreadLocalMap only use simple arrays to store values? How to save the value if there is a hash collision?
  Answer: ThreadLocalMap only uses arrays to store Entry values. If there is a hash collision at position i, there will be position i + 1. If i + 1 is not empty, it will be postponed in turn until a vacant position is found.

Guess you like

Origin blog.csdn.net/qq_22136439/article/details/129021047