[concurrent programming] ThreadLocal is very simple

Keywords: Java Spring jvm

What is ThreadLocal

ThreadLocal is a bit like a Map type data variable. Each thread of a ThreadLocal type variable has its own copy, and the modification of this variable by one thread will not affect the values of other thread copies. Note that a ThreadLocal variable can only set one value.

ThreadLocal<String> localName = new ThreadLocal();
localName.set("name1");
String name = localName.get();

In thread 1, a ThreadLocal object localName is initialized, and a value is saved through the set method. At the same time, the previously set value can be obtained through localName.get() in thread 1, but if it is in thread 2, it will be a null.

Here is the source code of ThreadLocal:

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

It can be found that each thread has a ThreadLocalMap data structure. When the set method is executed, its value is saved in the threadLocals variable of the current thread. When the get method is executed, it is obtained from the threadLocals variable of the current thread (the key value of ThreadLocalMap is ThreadLocal type)

Therefore, the set value in thread 1 cannot be touched by thread 2, and if it is reset in thread 2, it will not affect the value in thread 1, ensuring that threads will not interfere with each other.

The above mentioned variables of threadload are stored in the variables of threadloadmap. The following shows the relationship between Thread, threadload and threadloadmap.

The Thread class has the property variable threadLocals (the type is ThreadLocal.ThreadLocalMap). That is to say, each Thread has its own ThreadLocalMap, so each Thread reads and writes to this ThreadLocal in isolation, and will not affect each other. A ThreadLocal can only store one Object object. If you need to store multiple Object objects, you need to store multiple ThreadLocal!

ThreadLocal usage scenario

Having said the principle of ThreadLocal, let's take a look at the use scenario of ThreadLocal.

1. Save thread context information, which can be obtained anywhere you need
For example, when we use Spring MVC, we want to use HttpServletRequest in the Service layer. One way is to pass this variable to the Service layer at the Controller layer, but this method is not elegant enough. Spring has long helped us to think of this situation, and provides 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 above code is to use ThreadLocal to implement variable passing through the thread.

2. Ensure thread safety and improve performance in some cases
Performance monitoring, such as recording the processing time of the request to get some slow requests (such as processing time more than 500 milliseconds), so as to improve the performance. Here we take the interceptor function of Spring MVC as the column.

public class StopWatchHandlerInterceptor extends HandlerInterceptorAdapter {  
    //NamedThreadLocal is Spring's encapsulation of ThreadLocal. The principle is the same
    //In the case of multithreading, 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);
        //Continuation 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 thread bound local variable (start time)  
        long consumeTime = endTime - beginTime;//3. Time consumed  
        if(consumeTime > 500) {//It is considered that requests with processing time more than 500ms are slow requests  
        //TODO logging to log file  
        System.out.println(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));  
        }          
    }  
}  

Note: in fact, to achieve the above functions, you can not use ThreadLocal (synchronization lock, etc.), but the above code does indicate that ThreadLocal is a good column for scenarios.

ThreadLocal best practices

As can be seen from the above figure, the key of Entry points to ThreadLocal, which indicates weak reference with dotted line. Let's take a look at ThreadLocalMap:

java object references include: strong references, soft references, weak references, virtual references.

Weak references are also used to describe unnecessary objects. When the JVM garbage collection, whether the memory is sufficient or not, the object is only associated with weak references, then it will be recycled. When only the key of Entry in ThreadLocalMap points to ThreadLocal, ThreadLocal will recycle!!!
After the ThreadLocal is garbage collected, the key value of the corresponding Entry in the ThreadLocalMap will become null, but the Entry is a strong reference, so the Object stored in the Entry cannot be recycled, so there is a risk of memory leakage in the ThreadLocalMap.

So as a best practice, we should actively call the remove method to clean up when we are not using it. Here is a proposal:

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

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

}

Reference resources

  • https://mp.weixin.qq.com/s/1ccG1R3ccP0_5A7A5zCdzQ
  • https://mp.weixin.qq.com/s/SNLNJcap8qmJF9r4IuY8LA

Posted by prasadharischandra on Mon, 11 Nov 2019 04:56:55 -0800