JUC: Application and principle of ThreadLocal

Series article directory

JUC article: The realization principle of volatile visibility
JUC articles: the application and implementation principle of synchronized
JUC article: Implement a simple thread pool in Java
JUC article: thread pool in java

Article directory

  • Series Article Directory
  • 1. Concurrency issues
  • 2. Example of using ThreadLocal
  • 3. The realization principle of ThreadLocal
    • 1.set(T value)
    • 2.T get()
    • 3. void remove()
  • 4. Improper use of ThreadLocal may lead to memory leaks
    • 1. Why memory leaks occur
  • Summarize

1. Concurrency issues

Concurrency issues are particularly prone to occur when multiple threads access the same shared variable, especially when multiple threads need to write to a shared variable. In order to ensure thread safety, general users need to perform appropriate synchronization when accessing shared variables, as shown in Figure 1-3.

The synchronization method is generally to add a lock, which requires the user to have a certain understanding of the lock, which obviously increases the burden on the user. So is there a way to do it, when a variable is created, when each thread accesses it, it accesses the variable of its own thread? In fact, ThreadLocal can do this, although ThreadLocal did not appear to solve this problem.

ThreadLocal variable, then each thread accessing this variable will have a local copy of this variable. When multiple threads operate on this variable, the actual operation is the variable in their own local memory, thus avoiding thread safety issues. After creating a ThreadLocal variable, each thread will copy a variable to its own local memory, as shown in Figure 1-4.

2. Example of using ThreadLocal

This example starts two threads, sets the value of the local variable inside each thread, and then calls the print function to print the value of the current local variable. If the remove method of the local variable is called after printing, the variable in the local memory will be deleted, the code is as follows.

public class ThreadLocalsTest {<!-- -->

    //(2) Create ThreadLocal
    private static ThreadLocal<String> localvariable=new ThreadLocal<>();

    //(1) print function
    public static void print(String str){<!-- -->
        //1.1 Print the value of localvariable in the local memory of the current thread
        System.out.println(str + ":" + localvariable.get());
        //1.2 Clear the value of localvariable in the local memory of the current thread
        //localvariable. remove();
    }

    public static void main(String[] args) {<!-- -->
        //(3) Create thread one
        Thread threadOne = new Thread(new Runnable() {<!-- -->
            @Override
            public void run() {<!-- -->
                //3.1 Set local variables in thread one
                localvariable.set("threadOne local variable");
                //3.2 Call the print function
                print("threadOne");
                //3.3 print local variable value
                System.out.println("threadOne remove after:" + localvariable.get());

            }
        });

        //(4) Create thread Two
        Thread threadTwo = new Thread(new Runnable() {<!-- -->
            @Override
            public void run() {<!-- -->
                //4.1 Set local variables in thread Two
                localvariable.set("threadTwo local variable");
                //4.2 Call the print function
                print("threadTwo");
                //4.3 print local variable value
                System.out.println("threadTwo remove after:" + localvariable.get());

            }
        });
//(5) Start the thread
        threadOne. start();
        threadTwo. start();

    }
}

The result of the operation is as follows
threadOne: threadOne local variable
threadTwo: threadTwo local variable
threadOne remove after: threadOne local variable
threadTwo remove after: threadTwo local variable

Code (2) creates a ThreadLocal variable.
Codes (3) and (4) create threads One and Two respectively.
Code (5) starts two threads.

Code 3.1 in thread One sets the value of localVariable through the set method, which actually sets a copy in the local memory of thread One, which cannot be accessed by thread Two. Then code 3.2 calls the print function, and code 1.1 obtains the value of localVariable in the local memory of the current thread (thread One) through the get function.
Thread Two executes similarly to thread One. After opening the comment of code 1.2, run it again, and the result is as follows.

threadOne: threadOne local variable
threadTwo: threadTwo local variable
threadOne remove after:null
threadTwo remove after:null

3. Implementation principle of ThreadLocal

First look at the class diagram structure of ThreadLocal related classes, as shown in Figure 1-5.

It can be seen from the figure that there is a threadLocals and an inheritableThreadLocals in the Thread class, both of which are variables of type ThreadLocalMap, and ThreadLocalMap is a customized Hashmap.

By default, these two variables in each thread are null, and they will be created only when the current thread calls the set or get method of ThreadLocal for the first time. In fact, the local variables of each thread are not stored in the ThreadLocal instance, but in the threadLocals variable of the calling thread. That is to say, local variables of type ThreadLocal are stored in specific thread memory space.
ThreadLocal is a tool shell. It puts the value into the threadLocals of the calling thread through the set method and stores it. When the calling thread calls its get method, it is taken out from the threadLocals variable of the current thread for use. . If the calling thread never terminates, the local variable will always be stored in the threadLocals variable of the calling thread, so when the local variable is not needed, the local variable can be deleted from the threadLocals of the current thread by calling the remove method of the ThreadLocal variable.

In addition, why is threadLocals in Thread designed as a map structure?

Obviously because each thread can be associated with multiple ThreadLocal variables.

Analyze the implementation of ThreadLocal’s set, get and remove methods

1.set(T value)

public void set(T value) {<!-- -->
    //(1). Get the current thread
        Thread t = Thread. currentThread();
    //(2.) Use the current thread as the key to find the thread variable (ThreadLocalMap)
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            //(3.) For the first call, if ThreadLocalMap does not exist, create a ThreadLocalMap corresponding to the current thread
            createMap(t, value);
    }

Code (1) first obtains the calling thread, and then uses the current thread as a parameter to call the getMap(t) method. The code of getMap(Threadt) is as follows.

ThreadLocalMap getMap(Thread t) {<!-- -->
        return t. threadLocals;
    }

It can be seen that the function of getMap(t) is to obtain the thread’s own variable threadLocals(ThreadLocalMap), and the threadlocal variable is bound to the member variable of the thread.

If the return value of getMap(t) is not empty, set the value to threadLocals, that is, put the current variable value into the memory variable threadLocals of the current thread. threadLocals is a HashMap structure, where the key is the instance object reference of the current ThreadLocal, and the value is the value passed through the set method.

If getMap(t) returns a null value, it means that the set method is called for the first time, and the threadLocals variable of the current thread is created at this time. Let’s see what createMap(t,value) does.

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

2.T get()

public T get() {<!-- -->
    // get the current thread
        Thread t = Thread. currentThread();
    //Get the ThreadLocalMap of the current thread
        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;
            }
        }
    //(7) If threadLocals is empty, initialize the threadLocals member of the current thread
        return setInitialValue();
    }

The code first obtains the current thread instance, if the threadLocals variable of the current thread is not null, then directly returns the local variable bound to the current thread, otherwise executes the code (7) to initialize. The code of setinitialValue() is as follows.

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

If the threadLocals (ThreadLocalMap) variable of the current thread is not empty, set the local variable value of the current thread to null, otherwise call the createMap method to create the createMap variable of the current thread.

3. void remove()

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

As shown in the above code, if the threadLocals variable of the current thread is not empty, the local variable of the specified ThreadLocal instance in the current thread is deleted.

Summary: As shown in Figure 1-6, there is a member variable named threadLocals (ThreadLocalMap) inside each thread. The type of this variable is HashMap, where the key is the this reference of the ThreadLocal variable we defined, and the value is We set the value using the set method. The local variables of each thread are stored in the thread’s own memory variable threadLocals (ThreadLocalMap)

If the current thread does not die, these local variables will always exist, so it may cause memory overflow, so remember to call the remove method of ThreadLocal to delete the local variables in the threadLocals of the corresponding thread after use.

4. Improper use of ThreadLocal may lead to memory leak

1. Why there is a memory leak

ThreadLocal is just a tool class, and the specific storage variable is the threadLocals variable of the thread. threadLocals is a variable of ThreadLocalMap type, which is shown in the figure

As can be seen from the figure, the inside of ThreadLocalMap is an Entry array, Entry inherits from WeakReference, and the value inside Entry is used to store the value passed through the set method of ThreadLocal

When a thread calls the set method of ThreadLocal to set a variable, a record will be stored in the ThreadLoca!Map of the current thread. The key of this record is the weak reference of ThreadLocal, and the value is the set value. If the current thread has always existed and the remove method of ThreadLocal has not been called, and there are other objects in other places at this time
ThreadLocal reference, the ThreadLoca!Map variable of the current thread will have a reference to the ThreadLocal variable and a reference to the value object, which will not be released, which will cause a memory leak.

Considering that the ThreadLocal variable has no other strong dependencies and the current thread still exists, since the key in the ThreadLoca!Map of the thread is a weak dependency, the weak reference of the ThreadLocal variable in the ThreadLocaLMap of the current thread will be recycled during gc. But the corresponding value will still cause a memory leak, because at this time, there will be an entry item whose key is null but the value is not null in ThreadLoca!Map.

In fact, in the set, get and remove methods of ThreadLocal, you can find some opportunities to clean up these entry items whose key is null, but these cleanups do not have to happen.

Summary: The key in the Entry of ThreadLocalMap uses a weak reference to the ThreadLocal object, which is an improvement in avoiding memory leaks, because if it is a strong reference, even if there is no reference to the ThreadLocal object elsewhere, the ThreadLocal object in the ThreadLocalMap is still Will not be recycled, and if it is a weak reference, the ThreadLocal reference will be recycled. However, the corresponding value cannot be recycled. At this time, there will be an entry item in the ThreadLocalMap where the key is null but the value is not null. Although ThreadLocalMap provides set, get and remove methods, you can Clean up these Entry items, but this is not timely, and it will not be executed every time, so in some cases, memory leaks will still occur, so calling the remove method in time after is used is the solution to memory leaks Questions are king.

Summary

The ThreadLocal provided by Java provides us with convenience in programming, but if used improperly, it will also bring us
Trouble, so to develop good coding habits, after using the ThreadLocal variable in the thread, remember to clear it in time
get rid of.