ThreadLocal source code analysis

Keywords: Java Concurrent Programming ThreadLocal

ThreadLocal principle

1, Introduction to ThreadLocal

ThreadLocal can realize that each thread has its own copy of local variables, and different threads will not interfere with each other. It mainly solves the problem of allowing each thread to bind its own value. By using the get() and set() methods, it can obtain the default value or change its value to the value of the copy stored in the current process, so as to avoid thread safety problems.

Description: to set a shared variable as a thread local variable, a copy of this variable will be created and saved in the ThreadLocal object in the form of < thread, value >!

2, Use of ThreadLocal

ThreadLocal class interface is very simple. There are only four methods. Let's learn about it first:

1,void set(Object value)

Code explanation: set the value of the thread local variable of the current thread.

2,public Object get()

Code explanation: this method returns the thread local variable corresponding to the current thread.

3,public void remove()

Code explanation: the purpose of deleting the value of the local variable of the current thread is to reduce the occupation of memory. This method is a new method in JDK 5.0.

4,protected Object initialValue()

Code explanation: return the initial value of the thread local variable. This method is a protected method, which is obviously designed to be covered by subclasses. If people are anxious, they can't eat hot tofu. If there is no set, calling get returns null.

**Note: * * in JDK5.0, ThreadLocal already supports generics, and the class name of this class has changed to ThreadLocal. API methods have also been adjusted accordingly. The new version of API methods are void set(T value), T get() and T initialValue(). The difference between Object and T: Object is a root class and a real class; T is a placeholder for a specific class. It is only valid in the compiler and will eventually be erased and replaced by Object.

Test case

We know that if thread safety processing is not carried out, the shared variable Content will have thread safety problems. How should we deal with it?

public class ThreadLocalTest {
   private String Content ;

    public String getContent() {
        return Content;
    }
    
    public void setContent(String content) {
        Content = content;
    }

    public static void main(String[] args) {
        ThreadLocalTest test = new ThreadLocalTest();
        for (int i = 1; i <= 5 ; i++) {
            new Thread(()->{
                test.setContent(Thread.currentThread().getName() + "Data");
                System.out.println(Thread.currentThread().getName() + "------>" + test.getContent());
            },"thread "+i).start();
        }
    }
}
Method 1: ThreadLocal

Create ThreadLocal object and do corresponding processing in set and get methods!

public class ThreadLocalTest {
    ThreadLocal<String> stl = new ThreadLocal<>() ;

   private String Content ;

    public String getContent() {
        return stl.get();
    }

    public void setContent(String content) {
        stl.set(content);
    }

    public static void main(String[] args) {
        ThreadLocalTest test = new ThreadLocalTest();
        for (int i = 1; i <= 5 ; i++) {
            new Thread(()->{
                test.setContent(Thread.currentThread().getName() + "Data");
                System.out.println(Thread.currentThread().getName() + "------>" + test.getContent());
            },"thread "+i).start();
        }
    }
}
Method 2: Synchronized

Just add a class lock

public class ThreadLocalTest {
    ThreadLocal<String> stl = new ThreadLocal<>() ;

   private String Content ;

    public String getContent() {
        return Content;
    }

    public void setContent(String content) {
        Content = content;
    }

    public static void main(String[] args) {
        ThreadLocalTest test = new ThreadLocalTest();
        for (int i = 1; i <= 5 ; i++) {
            new Thread(()->{
                synchronized (ThreadLocalTest.class){
                    test.setContent(Thread.currentThread().getName() + "Data");
                    System.out.println(Thread.currentThread().getName() + "------>" + test.getContent());
                }
            },"thread "+i).start();
        }
    }
}

3, ThreadLocal vs Synchronized

The Synchronized keyword can also prevent multi-threaded shared variable conflicts. But it is different from TheadLocal

SynchronizedThreadLocal
principleThe synchronization mechanism only provides a shared variable, allowing multiple threads to queue up for access, "exchanging time for space“An additional copy of variables is provided for each thread, which can be implemented at the same time. 1. Multi threads can access concurrently without interfering with each other, "exchanging space for time"“
emphasisMultithreaded access shared resource synchronizationData isolation between multiple threads

4, Application scenario transaction case

Simulate a simple transfer business

Party A: transfer 100 to Party B, balance - 100;

Party B: balance + 100

In the above transfer process, we need to ensure the consistency of transactions and isolate the connection from multithreading concurrency!

General solutions

Ensure that the linked objects of Service and Dao are the same by passing parameters and opening transactions to ensure the security of data. Lock Synchronized!

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-jcmzbxzo-163522848566) (JUC concurrent programming. assets/image-20211025175459199.png)]

General operation steps:

  • Add a parameter to the Dao layer's method
  • The Dao layer will no longer get connections from the connection pool
  • Note: the Dao layer does not release the connection
  • Lock to ensure concurrency security

Disadvantages of conventional solutions: improve code coupling and lose concurrency

Using ThreadLocal to resolve

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-r7d60w1q-163522848567) (JUC concurrent programming. assets/image-20211025205159206.png)]

This method does not lock or transfer parameters, which will not affect and ensure the efficiency and data security! Note: before closing the connection, you need to call the remove method to unbind! To prevent memory leakage

5, ThreadLocal internal structure

Internal structure of JDK8

The benefits of this JDK design are: 1. Fewer nodes will be saved in each Map, which will reduce Hash conflicts

2. When the Thread is destroyed, the ThreadLocalMap will be automatically destroyed to reduce the memory occupation!

ThreadLocal source code

1. set method analysis
    public void set(T value) {
        Thread t = Thread.currentThread();		//Get current thread
        ThreadLocalMap map = getMap(t);		//Gets the ThreadLocalMap object of the thread
        if (map != null)		//map is not empty
            map.set(this, value);	//Set < ThreadLocal, variable copy > as entry
        else
            createMap(t, value);  //Create a map if the map is empty
    }

getMap

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;			//Returns the Map object of the current thread. threadlocals is the instance of the Map object!
    }

// ThreadLocal.ThreadLocalMap threadLocals = null;

createMap

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);		//Create a Map for the current thread
    }

Summary:

1. Get the current thread and get the map of the current thread

2. If the obtained Map is not empty, set parameters to the Map (use the current ThreadLocal reference as the key)

3. If the Map is empty, create a Map and set the initial value

2. get method parsing
    public T get() {
        Thread t = Thread.currentThread(); 	//Get current thread
        ThreadLocalMap map = getMap(t);	//Gets the ThreadLocalMap object of the current thread
        
        if (map != null) {		//map is not empty
            ThreadLocalMap.Entry e = map.getEntry(this); //Through key, i.e. ThreadLocal
            												//Take out the corresponding variable copy < ThreadLocal, copy variable >
            if (e != null) {	//Node is not empty
                @SuppressWarnings("unchecked")
                T result = (T)e.value;		//Fetch copy variable
                return result;	//Returns a copy of the variable
            }
        }
        return setInitialValue(); //Map is empty, initialize ma a map
    }

getMap

   ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;		//Returns the ThreadLocal object of the current thread
    }

getEntry

private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

setInitialValue (core method)

    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;  //Return initial value
    }

Inductive summary

1. Get the current thread, and then get the Map of the current thread

2. If the map is not empty, obtain an Entry node through the key (the required replica variable). If the node is not empty, obtain the replica variable through the node

3. Map is empty, initialize a map

3. remove method analysis
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread()); //Gets the map of the current thread
         if (m != null)	//map is not empty
             m.remove(this);	//Delete one of Threadlocal
     }
4,initialValue
  protected T initialValue() {
        return null;			//It is used to be rewritten. After rewriting, rewrite the initial value so that the initial value is not null
    }

ThreadLocalMap

Relationship between ThreadLocal, ThreadLocalMap, Thread and Entry

  • Firstly, ThreadLocalMap stores Entry key value pairs in the form of array. It is a static internal class of Thread, and Entry is the static internal class of ThreadLocalMap. The key of Entry is ThreadLocal from new, and value is the value set in. Therefore, a Thread can have multiple ThreadLocal value key value pairs.
  • The JVM internally maintains a thread version of Map < thread, t > (through the set method of ThreadLocal object, the ThreadLocal object is put into threadloadmap as a key). When each thread needs to use this T, it uses the current thread to get it from the Map. In this way, each thread has its own independent variables

6, ThreadLocal memory leak problem

Thread Leak

What is a memory leak? Simply put, it means that something is in memory, but you forget where it is. It occupies a piece of memory, but it can't be recycled. When there are more and more such things, the memory is tight, which eventually leads to server downtime.

Tell a little story about memory leakage. During the Anti Japanese period, there were two underground parties A and B. A was on-line and B was off-line. B could not directly contact the Party Central Committee. He needed a to help deliver messages. Once an accident happens to a, the Party Central Committee can't find B. B always exists, but in the vast crowd, the Party Central Committee can't enable B to arrange combat tasks. This situation is similar to memory leakage

  • That is, the memory occupied by objects or variables that will no longer be used cannot be recycled, which is a memory leak.

1. Strong, soft, weak, virtual and four references

1. Strong reference

Strong references are the most commonly used references. If an object has a strong reference, the garbage collector will never recycle it.

Object o=new Object();   //  Strong reference
o=null;     // Help the garbage collector recycle this object

2. Soft reference

If an object has only soft references, the memory space is enough, and the garbage collector will not recycle it; If the memory space is insufficient, the memory of these objects will be reclaimed. As long as the garbage collector does not recycle it, the object can be used by the program. Soft references can be used to implement memory sensitive caching.

//When we run out of memory, soft will be recycled
SoftReference<MyObject> softReference = new SoftReference<>(new Object());

3. Weak reference

For objects with only weak references, as soon as the garbage collection mechanism runs, the memory occupied by the object will be reclaimed regardless of whether the memory space of the JVM is sufficient.

//Once the garbage collection mechanism is running, it will recycle the memory occupied by the object
WeakReference<MyObject> weakReference = new WeakReference<>(new Object());

4. Phantom reference

As the name suggests, it is virtual. Unlike other references, virtual references do not determine the life cycle of objects. If an object only holds virtual references, it may be recycled by the garbage collector at any time, just as it does not have any references. It cannot be used alone or access objects through it.

The virtual reference must be used in conjunction with the reference queue. When the garbage collector prepares to recycle an object, if it finds that it still has a virtual reference, it will add the virtual reference to the associated reference queue before recycling the memory of the object.

ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue();
//It is associated with the reference queue. When the virtual reference object is recycled, it will enter the ReferenceQueue queue
PhantomReference<MyObject> phantomReference = new PhantomReference<>(new MyObject(),referenceQueue);

After understanding the above quotation, we know that,

2. Key == TheadLocal is a strong reference

Let's assume that when the key is a strong reference to ThreadLocal, will a memory leak occur?

ThreadLocal Ref is a weak reference, but the key in our ThreadLocalMap strongly references this ThreadLocal, because the weak reference will be automatically recycled, and all our ThreadLocal will be automatically recycled by GC after use, but the key in our ThreadLocalMap is a strong reference. If the Entry node is no longer deleted before the weak reference ends, At this time, the ThreadLocal referenced by the key cannot be recycled!

A memory leak occurred!

In other words, the key in ThreadLocalMap uses strong reference, which can not completely avoid memory leakage

3. Key == ThreadLocal is a weak reference

Assuming that our key is a weak reference to ThreadLocal, will there be a memory leak?

Note the difference:

  • If it is a weak reference, after ThreadLocal is recycled, the key is set to null
  • If it is a strong reference, the key will not be modified after ThreadLocal is recycled

4. True cause of memory leak

1. This Entry is not manually deleted
2. CurrentThread the current thread is still running

  • The first point is easy to understand. As long as ThreadLocal is used and its remove method is called to delete the corresponding Entry, memory leakage can be avoided.
  • The second point is a little more complicated. Because ThreadLocalMap is a property of Thread and is referenced by the current Thread, the life cycle of ThreadLocalMap is as long as Thread. After using ThreadLocal, if the execution of the current Thread ends, the ThreadLocalMap will naturally be recycled by gc to avoid memory leakage at the root.

To sum up, the root cause of ThreadLocal memory leak is:

  1. This Entry is not manually deleted
  2. CurrentThread the current Thread is still running. Because the life cycle of ThreadLocalMap is as long as that of Thread, if the corresponding key is not manually deleted, memory leakage will occur, not because of weak reference

5. Reasons for using weak reference for key

No matter what type of reference the key in ThreadLocalMap uses, memory leakage cannot be completely avoided, which has nothing to do with the use of weak references.

In fact, in the set/getEntry method in ThreadLocalMap, it will judge that the key is null (that is, ThreadLocal is null). If it is null, it will set the value to null

This means that after using ThreadLocal, the currentthread is still running. Even if you forget to call the remove method * *, the weak reference can provide one more guarantee than the strong reference: the ThreadLocal of the weak reference will be recycled. The corresponding value will be cleared the next time ThreadLocaIMap calls any method in set/get/remove, so as to avoid memory leakage**

Reference article:

Posted by rubbertoad on Mon, 25 Oct 2021 23:39:38 -0700