Basic meaning

ThreadLocal is literally a thread-local variable, which provides an independent, non-interfering local variable for each thread.

  1. The ThreadLocal class is a generic class, meaning that local variables can be of any type, such as Long, List, and so on.
  2. The ThreadLocal class provides get and set methods to get and change the value of this local variable over the lifetime of the thread.
  3. The thread-local variables of each thread are independent of each other.
  4. ThreadLocal variables can provide an initialization method. For ThreadLocal variables that have no value in the current thread, initialValue is called on the first get(). This method is a deferred call method.

Here’s a simple example to illustrate the use of ThreadLocal:

public class ThreadLocalDemo2 {
    public static class MyRunnable implements Runnable {
        private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
            protected Integer initialValue() {
                return1; }}; @Override public voidrun() {
            threadLocal.set((int) (Math.random() * 100D));
            System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
        }
    }

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();

        Thread t1 = new Thread(myRunnable, "A");
        Thread t2 = new Thread(myRunnable, "B"); t1.start(); t2.start(); } /** B:48 A:32 that is, thread A and thread B hold integer variables independent of each other, as long as each thread internal usesetMethod, and then use get inside the thread to get the corresponding value. * /}Copy the code

Realize the principle of

ThreadLocal set(T value), get(

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! = null) map.set(this, value);else
            createMap(t, value);
    }
    
    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;
                returnresult; }}return setInitialValue();
    }
    
    public static native Thread currentThread();
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
Copy the code
  1. The native currentThread() method is used to fetch the current running thread, and then the threadLocals field of type ThreadLocalMap on the currentThread t object instance.
  2. For the set method, if there is a direct set value after the ThreadLocalMap is retrieved on the thread; If not, initialize a Map based on the value of set.
  3. For the get method, if there is a ThreadLocalMap on the thread, directly get the value and type conversion; If not, call the setInitialValue() method to set and return the initial value.

Note:

The actual values of the variables stored by ThreadLocal are stored on the member variables of the Thread class through the ThreadLocalMap structure, which means that each Java Thread, an object instance of the Thread class, has its own ThreadLocalMap.

ThreadLocalMap

ThreadLocalMap is a static inner class in threadLocal.java that is a hash table customized to maintain thread-local variables.

static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; }}... private Entry[] table; . }Copy the code
  1. The Key of this ThreadLocalMap is an instance of the generic ThreadLocal class and the Value is the ThreadLocal variable T to store.

  2. The actual ThreadLocal variable T is stored in the table as an Entry along with an instance of ThreadLocal.

  3. Note that threadlocalmap. Entry inherits WeakReference, making the instance as Key ThreadLocal a WeakReference. Therefore, when only weakly referenced instances of ThreadLocal exist and are scanned by the GC thread, the GC reclaims the memory of the ThreadLocal instance. At this point, the corresponding Key value is null.

  4. Map is designed here because a thread may have multiple thread-local variables, i.e. object instances of multiple ThreadLocal.

ThreadLocalMap hash conflict

ThreadLocalMap is a custom hash table. Since it’s a hash table, we need to solve the problem of hash conflicts. In the case of java.util.HashMap, the way to resolve conflicts is the zip method.

        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; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }Copy the code

ThreadLocalMap resolves the conflict using open addressing.

Take the set operation as an example. In simple terms, after a Hash is performed on a Key, if the Hash result is found to be different from the set Key, the user searches for the set Key until an empty position is found.

Well, is it possible not to find it?

The answer is no.

  1. The nextIndex method in the source code ensures that the index reaches the end of the array and then looks at the beginning of the table array.
  2. If not, the current table array is full. However, the source code guarantees that resize will be triggered every time the array size reaches threshold. Therefore, the expansion must occur before the table array becomes full.

Side effects of open addressing

Bad side effects Due to the use of open addressing, the set, GET, and remove operations of ThreadLocalMap cannot find the correct location in a hash addressing determination. It takes O(n) more time to readdress to find an empty location or a location that can be retrieved or deleted.

Good side effects THE JDK source authors take advantage of the loop of secondary addressing brought about by open addressing in another way. In the secondary addressing loop of the set and GET methods, if a stale entry is found, it is cleaned up.

  • ThreadLocalMap -> set -> replaceStaleEntry -> expungeStaleEntry

  • ThreadLocalMap -> get -> getEntry -> getEntryAfterMiss -> 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--; . }Copy the code

Note: This approach does not guarantee that each threadLocalmap. set or GET operation will clean up all entries whose keys have been reclaimed. To take an extreme counter example, a ThreadLocalMap has an entry with a null key, but the first hash of a GET operation directly finds the correct location without a second search. Then, no cleanup can be done at this point.

Why does ThreadLocal use WeakReference

Mentioned above, ThreadLocalMap: : Entry: : ThreadLocal is a weak reference. So why use WeakReference?

Here, let’s think the other way around. Instead of using weak references, let’s use strong references. Thus, all defined ThreadLocal variables persist for the lifetime of the thread, even if the user no longer uses ThreadLocal variables, because the following two reference chains persist:

  • ThreadLocalRef->Thread->ThreadLocal->ThreadLocalMap->Entry->key

  • ThreadLocalRef->Thread->ThreadLocal->ThreadLocalMap->Entry->value

So, unless the user does a manual ThreadLocalMap::remove, the occupied space will never be freed.

In summary, the reason for using weak references with ThreadLocalMap->Entry->key(i.e. ThreadLocal) is that if the user does not manually perform ThreadLocalMap::remove, It also gives the system a way to partially clean up resources during set and GET. Although the JVM only cleans up keys, subsequent JDK source code designs provide a mechanism for cleaning up values and entire entries (removing strong references to values and entries in ThreadLocalMap). However, this mechanism may not work.

So, each time it is determined that ThreadLocal is no longer in use, its remove() method is manually called for data removal.

Otherwise, memory leaks may occur.

Memory leaks

When ThreadLocal variables hold only weak references, they are flushed out of memory if they undergo GC. The ThreadLocal key of the ThreadLocalMap is then null. However, the corresponding value has not been recovered because the chain of strong reference relationship written above still exists. A memory leak occurs when a value is not fetched or used, but cannot be reclaimed.

Application scenarios

Here are a few examples:

  1. For example, if you are dealing with a very complex business in a thread, and there may be many methods, using ThreadLocal can replace explicit passing of some parameters.
  2. For example, to store user sessions. The Session feature is well suited to ThreadLocal because it is valid for the duration of the current Session prior to the Session and is destroyed at the end of the Session. Let’s start with a general but incorrect analysis of the web request process:
  • A user accesses a Web page in a browser;
  • The browser makes a request to the server;
  • A service handler on the server (such as Tomcat) receives the request and starts a thread to process the request, using a Session.
  • Finally, the server returns the request results to the client browser.

From this simple access process, we can see that the Session is generated and used in the process of processing a user’s Session. If we simply understand that a user’s Session corresponds to an independent processing thread on the server, then using ThreadLocal to store the Session is quite appropriate. However, server software such as Tomcat adopts the thread pool technology, instead of strictly speaking, one session corresponds to one thread. This is not to say that ThreadLocal is not suitable for this situation, but rather to clean up the previous Session each time a request comes in, usually using interceptors and filters.

Finally, please click “like” and “support”

Reference and thanks

  1. Juejin. Cn/post / 684490…
  2. Blog.csdn.net/eson_15/art…
  3. www.imooc.com/article/267…
  4. www.cnblogs.com/fengzheng/p…
  5. www.jqhtml.com/58671.html