The most complete ThreadLocal explanation in history

Posted by zaiber on Wed, 05 Jan 2022 02:43:26 +0100

1, Introduction to ThreadLocal

ThreadLocal is called thread variable, which means that the variable filled in ThreadLocal belongs to the current thread and is isolated from other threads, that is, the variable is unique to the current thread. ThreadLocal creates a copy of the variable in each thread, so each thread can access its own internal copy variable.

Threadload variable, Thread local variable. Objects contained in the same ThreadLocal have different copies in different threads. Here are a few points to note:

  • Because each Thread has its own instance copy, and the copy can only be used by the current Thread. This is also the origin of ThreadLocal naming.
  • Since each Thread has its own instance copy and other threads are inaccessible, there is no problem of sharing among multiple threads.

ThreadLocal provides an instance of a local thread. The difference between it and ordinary variables is that each thread using this variable initializes a completely independent instance copy. ThreadLocal variable is usually modified by private static. When a thread ends, all relative instance copies of ThreadLocal used by it can be recycled.

In general, ThreadLocal is applicable to the scenario where each thread needs its own independent instance and the instance needs to be used in multiple methods, that is, variables are isolated between processes and shared between methods or classes

The following figure can enhance understanding:


Figure 1-1 ThreadLocal status during use

2, Difference between ThreadLocal and Synchronized

ThreadLocal is actually a variable bound to a thread. Both ThreadLocal and synchronized are used to solve multi-threaded concurrent access.

However, ThreadLocal is fundamentally different from synchronized:

1. Synchronized is used for data sharing between threads, while ThreadLocal is used for data isolation between threads.

2. Synchronized uses a lock mechanism so that variables or code blocks can only be accessed by one thread at a time. ThreadLocal provides a copy of the variable for each thread

, so that each thread does not access the same object at a certain time, which isolates the data sharing of multiple threads.

Synchronized, on the contrary, is used to obtain data sharing when communicating among multiple threads.

In a word, understand ThreadLocal. To store things in ThreadLocal is to store things in its Map, and then ThreadLocal hangs the Map under the current thread, so that the Map only belongs to this thread.

3, Simple use of ThreadLocal

Direct code:

public class ThreadLocaDemo {
 
    private static ThreadLocal<String> localVar = new ThreadLocal<String>();
 
    static void print(String str) {
        //Prints the value of a local variable in local memory in the current thread
        System.out.println(str + " :" + localVar.get());
        //Clear local variables in local memory
        localVar.remove();
    }
    public static void main(String[] args) throws InterruptedException {
 
        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("local_A");
                print("A");
                //Print local variables
                System.out.println("after remove : " + localVar.get());
               
            }
        },"A").start();
 
        Thread.sleep(1000);
 
        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("local_B");
                print("B");
                System.out.println("after remove : " + localVar.get());
              
            }
        },"B").start();
    }
}
 
A :local_A
after remove : null
B :local_B
after remove : null

From this example, we can see that two thread sub tables obtain the variables stored by their own threads, and the acquisition of variables between them will not be disordered. This can also be understood in combination with Figure 1-1. I believe there will be a deeper understanding.

4, Principle of ThreadLocal

If you want to see the principle, you have to start from the source code.

4.1 set() method of ThreadLocal:

public void set(T value) {
        //1. Get current thread
        Thread t = Thread.currentThread();
        //2. Get the property threadLocalMap in the thread. If threadLocalMap is not empty,
        //Directly update the variable value to be saved, otherwise create threadLocalMap and assign value
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            // Initialize thradLocalMap and assign value
            createMap(t, value);
}

As can be seen from the above code, when assigning ThreadLocal set, first get the thread of the current thread and get the threadLocalMap attribute in the thread thread. If the map attribute is not empty, the value value is updated directly. If the map is empty, the threadLocalMap is instantiated and the value value is initialized.

So what is ThreadLocalMap and how does createMap do it? Let's continue to look. Finally, you will have a deeper understanding of the source code on the idea.

  static class ThreadLocalMap {
 
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
 
        
    }

It can be seen that ThreadLocalMap is the internal static class of ThreadLocal, and its composition mainly uses Entry to save data, and it is also an inherited weak reference. Inside the Entry, use ThreadLocal as the key and the value we set as the value. The details should be told by ourselves.

//This is the internal method of threadlocal
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
 
 
    //ThreadLocalMap construction method
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

4.2 get method of ThreadLocal

    public T get() {
        //1. Get current thread
        Thread t = Thread.currentThread();
        //2. Gets the ThreadLocalMap of the current thread
        ThreadLocalMap map = getMap(t);
        //3. If the map data is empty,
        if (map != null) {
            //3.1. Get the value stored in threalLocalMap
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //If the data is null, it will be initialized. As a result of initialization, the key value stored in TheralLocalMap is threadLocal and the value is null
        return setInitialValue();
    }
 
 
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;
    }

4.3 remove method of ThreadLocal

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

The remove method directly deletes the value corresponding to ThrealLocal from the ThreadLocalMap in the current Thread. Why delete? This involves the problem of memory leakage.

In fact, the key used in ThreadLocalMap is the weak reference of ThreadLocal. The feature of weak reference is that if this object has only weak reference, it will be cleaned up in the next garbage collection.

Therefore, if ThreadLocal is not strongly referenced by the outside, it will be cleaned up during garbage collection. In this way, the key using this ThreadLocal in ThreadLocalMap will also be cleaned up. In this way, the key will be referred to as null, but value will not appear.

ThreadLocal is actually a variable bound to the thread, so there will be a problem: if the variable in ThreadLocal is not remove d or replaced, its life cycle will coexist with the thread. Generally, the thread management in the thread pool adopts the method of thread reuse. The thread in the thread pool is difficult to end or will never end, which means that the thread duration will be unpredictable and even consistent with the life cycle of the JVM. For example, if a collection class or complex object is directly or indirectly wrapped in ThreadLocal, and the content is operated after the object is taken out in the same ThreadLocal each time, the space occupied by the internal collection class and complex object may continue to expand.

4.4 relationship between ThreadLocal and Thread, ThreadLocalMap

Figure 4-1 data relationship among thread, THreadLocal and ThreadLocalMap

From this figure, we can intuitively see that ThreadLocalMap is actually an attribute value of Thread thread, and ThreadLocal maintains ThreadLocalMap

This property refers to a tool class. Thread threads can have multiple shared variables maintained by ThreadLocal that are exclusive to their own threads (this shared variable is only shared for their own threads)

5, Common usage scenarios for ThreadLocal

As mentioned above, ThreadLocal is suitable for the following two scenarios

  • 1. Each thread needs to have its own separate instance
  • 2. Instances need to be shared in multiple methods, but do not want to be shared by multiple threads

For the first point, each thread has its own instance, and there are many ways to implement it. For example, you can build a separate instance inside a thread. ThreadLoca can meet this requirement in a very convenient form.

For the second point, it can be implemented in the form of reference passing between methods under the condition of meeting the first point (each thread has its own instance). ThreadLocal makes the code coupling lower and the implementation more elegant.

scene

1) Store user Session

A simple example of using ThreadLocal to store sessions:

private static final ThreadLocal threadSession = new ThreadLocal();
 
    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
}

Scenario 2: database connection and database transaction processing

Scenario 3: data cross layer transfer (controller,service, dao)

Each thread needs to store information similar to global variables (such as the user information obtained in the interceptor), which can be used directly by different methods to avoid the trouble of parameter transmission, but it does not want to be shared by multiple threads (because the user information obtained by different threads is different).

For example, ThreadLocal is used to save some business contents (user permission information, user name obtained from the user system, user ID, etc.), which are the same in the same thread, but the business contents used by different threads are different.

During the thread life cycle, you can get the object you set through the get() method of this static ThreadLocal instance, avoiding the trouble of passing this object (such as user object) as a parameter.

For example, we are a user system. When a request comes in, a thread will be responsible for executing the request, and then the request will call service-1(), service-2(), service-3(), and service-4(). These four methods may be distributed in different classes. This example is somewhat like storing a session.

package com.kong.threadlocal;
 
 
public class ThreadLocalDemo05 {
    public static void main(String[] args) {
        User user = new User("jack");
        new Service1().service1(user);
    }
 
}
 
class Service1 {
    public void service1(User user){
        //Assign a value to ThreadLocal, and subsequent services can be obtained directly through ThreadLocal.
        UserContextHolder.holder.set(user);
        new Service2().service2();
    }
}
 
class Service2 {
    public void service2(){
        User user = UserContextHolder.holder.get();
        System.out.println("service2 Get the user:"+user.name);
        new Service3().service3();
    }
}
 
class Service3 {
    public void service3(){
        User user = UserContextHolder.holder.get();
        System.out.println("service3 Get the user:"+user.name);
        //After the whole process is executed, be sure to execute remove
        UserContextHolder.holder.remove();
    }
}
 
class UserContextHolder {
    //Create ThreadLocal to save User object
    public static ThreadLocal<User> holder = new ThreadLocal<>();
}
 
class User {
    String name;
    public User(String name){
        this.name = name;
    }
}
 
Results of execution:
 
service2 Get the user:jack
service3 Get the user:jack

Scenario 4: Spring uses ThreadLocal to solve thread safety problems

We know that in general, only stateless beans can be shared in a multithreaded environment. In Spring, most beans can be declared as a singleton scope. It is because Spring uses ThreadLocal to encapsulate non thread safe "state objects" in some beans (such as RequestContextHolder, TransactionSynchronizationManager, LocaleContextHolder, etc.), making them thread safe "state objects". Therefore, stateful beans can work normally in multithreading in a singleton manner.

General Web applications are divided into three layers: presentation layer, service layer and persistence layer. Corresponding logic is written in different layers, and the lower layer opens function calls to the upper layer through interfaces. In general, all program calls from receiving the request to returning the response belong to the same thread, as shown in Figure 9-2.

In this way, users can store some non thread safe variables in ThreadLocal as needed. In the calling thread of the same request response, the same ThreadLocal variable accessed by all objects is bound by the current thread.

The following examples can reflect Spring's transformation idea of stateful beans:

Code listing 9-5 TopicDao: non thread safe

 
public class TopicDao {
   //① A non thread safe variable
   private Connection conn; 
   public void addTopic(){
        //② Reference non thread safe variables
	   Statement stat = conn.createStatement();
	   ...
}

Since conn at ① is a member variable and the addTopic() method is non thread safe, you must create a new TopicDao instance (non singleton) when using it. Next, use ThreadLocal to transform Conn, a non thread safe "state":

Code listing 9-6 TopicDao: thread safety

 
import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {
 
  //① Use ThreadLocal to save the Connection variable
private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();
public static Connection getConnection(){
         
	    //② If connThreadLocal does not have a Connection corresponding to this thread, create a new Connection,
        //And save it to the thread local variable.
if (connThreadLocal.get() == null) {
			Connection conn = ConnectionManager.getConnection();
			connThreadLocal.set(conn);
              return conn;
		}else{
              //③ Directly return thread local variables
			return connThreadLocal.get();
		}
	}
	public void addTopic() {
 
		//④ Get the corresponding thread from ThreadLocal
         Statement stat = getConnection().createStatement();
	}

When different threads use TopicDao, first judge connthreadlocal Whether get() is null. If it is null, it indicates that the current thread does not have a corresponding Connection object. In this case, create a Connection object and add it to the local thread variable; If it is not null, it indicates that the current thread already has a Connection object and can be used directly. This ensures that different threads use thread related connections instead of those of other threads. Therefore, the topic Dao can be shared by a singleton.

Of course, this example itself is very rough. Putting the ThreadLocal of the Connection directly in Dao can only avoid thread safety problems when multiple methods of this Dao share a Connection, but it cannot share the same Connection with other Dao. To share the same Connection with multiple Dao in the same transaction, you must use ThreadLocal to save the Connection in a common external class. But this example basically illustrates Spring's solution to thread safety of stateful classes. Later in this chapter, we will explain in detail how Spring solves the problem of transaction management through ThreadLocal.

Follow up supplement

reference resources:

https://www.cnblogs.com/zz-ksw/p/12684877.html

http://baijiahao.baidu.com/s?id=1653790035315010634&wfr=spider&for=pc

https://www.jianshu.com/p/f956857a8304

https://www.cnblogs.com/luxiaoxun/p/8744826.html

https://www.cnblogs.com/luxiaoxun/p/8744826.html

https://www.jianshu.com/p/f956857a8304

https://blog.csdn.net/u012190520/article/details/80974458

Topics: Java Back-end ThreadLocal