Java Multithreading 9: ThreadLocal Principles and Scenarios

Foreword: Interviewers often ask what ThreadLocal is, and they want to examine how well candidates understand the definition, principles, and scenarios of ThreadLocal.

1. ThreadLocal Definition

ThreadLocal, the thread local variable, as its name implies, is a private local variable for each thread. Generally speaking, when you create a ThreadLocal variable, each thread copies a copy to local memory when it accesses the variable, so when you manipulate ThreadLocal variables under multiple threads, they each operate on their own copies, without interacting with each other.Thread security issues are avoided, however.

ThreadLocal is a bit like a Map-type data variable. ThreadLocal-type variables have their own copy of each thread. A thread's modification to this variable does not affect the value of other thread copies. ThreadLocal provides us with a new thread-safe idea. It is important to note that there is a ThreadLocal variable where only one value can be set.

As an example, you may see:

public class ThreadLocalTest {


    //Define a ThreadLocal variable, generic specified as Integer
    private ThreadLocalthreadLocal = new ThreadLocal() {
        @Override
        protected Integer initialValue() {
            //Initial value set to 1
            return 1;
        }
    };


    @Test
    public void testThreadLocal() {
        //Create thread thread1
        MyThread thread1 = new MyThread("thread1");
        //Start Thread 1
        thread1.start();


        //Create Thread 2
        MyThread thread2 = new MyThread("thread2");
        //Start Thread 2
        thread2.start();
    }


    class MyThread extends Thread {


        MyThread(String name) {
            super(name);
        }


        @Override
        public void run() {
            super.run();
            //Get the value of threadLocal and output
            System.out.println(getName() + "------ threadLocalValue is " + threadLocal.get());
            //Value of threadLocal + 1 and set to threadLocal
            threadLocal.set(threadLocal.get() + 1);
            //Get the threadLocal value again and output
            System.out.println(getName() + "------ after ++, threadLocalValue is " + threadLocal.get());
        }
    }
}

Output results:

The logcat output is as follows:

 thread1------ threadLocalValue is 1
 thread1------ after ++, threadLocalValue is 2
 thread2------ threadLocalValue is 1
 thread2------ after ++, threadLocalValue is 2

The simple example is that by opening two threads to manipulate the ThreadLocal variable and outputting from the console, you can prove the definition we mentioned above that each thread's access to and operation of the threadLocal variable has no effect on each other, thus isolating the threads.

2. ThreadLocal Principle

If you just answer how to use it, you're done, and apparently not in time for Java programmers to roll in. Next, tear the source code by hand:

  • Start with the Thread class

class Thread implements Runnable {
    ...//Omit irrelevant code
    //There is a private member variable for ThreadLocalMap within each thread
    ThreadLocal.ThreadLocalMap threadLocals = null;
     ...//Omit irrelevant code
    private void exit() {
        ...
        //The threadLocals variable is empty when the thread exits 
        threadLocals = null;
        ...
    }  
}

There are only two things to focus on in the entire Thread class. First, each Thread has a ThreadLocalMap type threadLocals variable. Second, the threadLocals are empty when the thread exits. When to put something inside, don't worry, it will be mentioned later.

You can see that each thread has a ThreadLocalMap data structure whose value is saved in the threadLocals variable of the current thread when the set method is executed and obtained from the threadLocals variable of the current thread when the get method is executed. (The ThreadLocalMap key value is of type ThreadLocal)

  • Take a look at ThreadLocal's internal class, ThreadLocalMap

//ThreadLocalMap functions like HashMap and maintains an Entry array internally to store data
static class ThreadLocalMap {
        //Entry is a K-V entity, key is a ThreadLcal variable, value is any type of variable
        static class Entry extends WeakReference<ThreadLocal> {
            //The value is bound to the ThreadLocal variable
            Object value;


            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        //Internally maintained Entry array
        private Entry[] table;


        //Construction method
        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            ... //Omit irrelevant code
            //Initialize Entry Array
            table = new Entry[INITIAL_CAPACITY];
            ... //Omit irrelevant code
        }
}

ThreadLocalMap is actually a data storage structure, similar to HashMap. It also maintains an Entry entity array inside. This Entry stores data in K-V form. key is a ThreadLocal variable. value supports any kind of variable. At present, it is not known what exactly it stores. Then look down.

  • ThreadLocal itself is finally coming to light and see what ThreadLocal's core method, setInitialValue / get / set, does

public class ThreadLocal{
   //Set Initial Value
   private T setInitialValue() {
         //Get the initial value by overriding the initialValue method
        T value = initialValue();
         //Get the current thread
        Thread t = Thread.currentThread();
         //Using the getMap method, pass in the current thread as a parameter to get the ThreadLocalMap object
        ThreadLocalMap map = getMap(t);
         //If the obtained ThreadLocalMap is not empty, it is saved directly, otherwise a ThreadLocalMap is created and saved.
        if (map != null)
            //key is threadLocal itself, value is the value we need to store in our business
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
  //Get the ThreadLocalMap object
  ThreadLocalMap getMap(Thread t) {
        //A key!!!
        //What's returned here is that each Thread mentioned above has a ThreadLocalMap member variable
        return t.threadLocals;
  }
  //Get the value of ThreadLocal
  public T get() {
        //Get Current Thread
        Thread t = Thread.currentThread();
        //As above, take the ThreadLocalMap variable for the current thread
        ThreadLocalMap map = getMap(t);
        //Find data in ThreadLocalMap and return
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
  //Set the value of ThreadLocal
  public void set(T value) {
        //Get Current Thread
        Thread t = Thread.currentThread();
        //Again, take the ThreadLocalMap variable for the current thread as above
        ThreadLocalMap map = getMap(t);
        //Store data
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
}

As you can see from the source code of ThreadLocal, the three core methods are all the same.Get the current thread because when we first analyzed the Thread class, we mentioned that each Thread had its own ThreadLocalMap member variable, so getting the current thread made it easy to access the ThreadLocalMap object of the current thread, so saving and fetching data was done directly by ThreadLocalMap. Because each thread got a ThreadLocalMap object differentlySo the stored data is threaded isolated, which is also the most important part of ThreadLocal.

As the source code above understands, the principle of ThreadLocal is no problem. Finally, a diagram is attached to summarize:

Each thread has its own ThreadLocalMap object. The ThreadLocalMap object maintains a K-V formatted Entry array. Key stores the ThreadLocal object itself, and value is the value that needs to be stored. When we want to manipulate the ThreadLocal variable, we get the current thread first, get the corresponding ThreadLocalMap object according to the current thread, and then manipulate the ThreadLocalMap internal number.Data stored in a group.

3. ThreadLocal Scenarios

After discussing the principles of ThreadLocal, let's look at the scenarios in which ThreadLocal is used.

3.1. Save thread context information and get it anywhere you want

For example, when using Spring MVC, we want to use HttpServletRequest at the Service layer. One way is to pass this variable to the Service layer at the Controller layer, but this is not elegant enough. Spring has already helped us think about this and provided ready-made tool classes:

public static final HttpServletRequest getRequest(){
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    return  request;
}

public static final HttpServletResponse getResponse(){
    HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
    return response;
}

The code above uses ThreadLocal to implement variables that are passed around the thread.

3.2. Ensure thread safety in some situations and improve performance

Performance monitoring, such as recording request processing time and getting some slow requests (such as more than 500 milliseconds of processing time), improves performance. Here we list the interceptor capabilities of Spring MVC.

public class StopWatchHandlerInterceptor extends HandlerInterceptorAdapter {  
    //NamedThreadLocal is Spring's encapsulation of ThreadLocal in the same way
    //In multithreaded cases, the startTimeThreadLocal variable must be isolated between each thread
    private NamedThreadLocal<Long>  startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {  
        //1. Start Time
        long beginTime = System.currentTimeMillis();  
        //Thread binding variable (this data is only visible to the currently requested thread)  
        startTimeThreadLocal.set(beginTime);
        //Continue the process  
        return true;
    }  
      
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {  
        long endTime = System.currentTimeMillis();//2. End Time  
        long beginTime = startTimeThreadLocal.get();//Get the thread bound local variable (start time)  
        long consumeTime = endTime - beginTime;//3. Time consumed  
        if(consumeTime > 500) {//Requests over 500 milliseconds are considered slow here  
        //TODO Logged to Log File  
        System.out.println(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));  
        }          
    }  
}  

Note: In fact, to achieve the above functions, ThreadLocal can be completely avoided, but the code above really indicates that ThreadLocal is a good column for scenes.

3.3. Best practices for ThreadLocal

As you can see from the figure above, Entry's key points to ThreadLocal and uses dashed lines to indicate weak references. Let's take a look at ThreadLocalMap:

References to java objects include strong references, soft references, weak references, and virtual references.

Weak references are also used to describe non-essential objects. When a JVM garbage collects, the object is only associated with a weak reference, regardless of memory availability. ThreadLocal recycles when only the Entry key in the ThreadLocalMap points to ThreadLocal!!!
When ThreadLocal is garbage collected, the key value of the corresponding Entry in ThreadLocalMap becomes null, but Entry is a strong reference, so Object s stored in Entry cannot be recycled, so ThreadLocalMap is at risk of memory leaks.

So the best practice is to actively call the remove method to clean up when we are not using it. Here is a suggested solution:

public class Dynamicxx {
    
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public void dosomething(){
        try {
             contextHolder.set("name");
            // Other business logic
        } finally {
            contextHolder .remove();
        }
    }

}

A brief summary

  • Inside each Thread object is a member variable of ThreadLoacalMap, which resembles a Map type in which the key refers to the this of the ThreadLocal variable we defined and the value is the value we set using the set method;

  • ThreadLocal instance objects stored in ThreadLocalMap may never be cleared if the thread does not die, so when we do not need to use the ThreadLocal value, we should call the remove method manually to clear it.

Reference link:

The interviewer asked again: What is ThreadLocal?

ThreadLocal is actually simple

Tags: Java

Posted on Wed, 08 Sep 2021 13:26:51 -0400 by MikeFairbrother