Deeply understand the principle of ThreadLocal and the problem of memory leakage

Posted by Louis-Platt on Fri, 14 Jan 2022 22:06:47 +0100

ThreadLocal principle

Before reading this article, you need to understand the concept of strong, weak and virtual in Java. The transmission address is: Detailed explanation of four references of strong, weak and virtual in Java

1, Introduction

  • It can solve the data security problem of multithreading and associate the current thread with a data (it can be a common variable, an object, an array, a collection, etc.)
  • Each thread can only access the data of its own thread and cannot operate the data of other threads. The data of each thread is independent of each other
  • ThreadLocal defines set, get and remove methods to associate / fetch / remove data
  • Each ThreadLocal object can only be associated with one data for the current thread. If you want to associate multiple data for the current thread, you need to use multiple ThreadLocal object instances

2, Quick start

public class ThreadLocalTest {
	public static void main(String[] args) throws InterruptedException {
		
        //Create ThreadLocal object instance
		final ThreadLocal<Integer> th = new ThreadLocal<Integer>();

		//t1 thread
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					th.set(100); //t1 thread stores data
					System.out.println("t1 Thread assigned value:" + th.get());
					Thread.sleep(2000); //t1 thread sleeps for 2 seconds. During the sleep period, t2 thread will run
					System.out.println("t1 Value obtained by thread:"+th.get());
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}).start();
		
		Thread.sleep(1000);

		//t2 thread
		new Thread(new Runnable() {
			@Override
			public void run() {
				Integer ele = th.get(); //t2 thread fetches data (null at this time, not related to t1)
				System.out.println("t2 Value obtained by thread:" + ele);
				th.set(200); //t2 thread assignment
				System.out.println("t2 Value obtained after thread assignment:"+th.get()); //The t2 thread gets the value from its own copy
			}
		}).start();
	}
}

Operation results:

3, Difference between ThreadLocal and synchronized

  • Different principles
    • synchronized
      • Only one piece of data is provided to allow different threads to compete for lock access
    • ThreadLocal
      • Each thread is provided with its own data, so as to achieve simultaneous access without interference with each other
  • Different emphasis
    • synchronized
      • Synchronization of accessing resources between multiple threads
    • ThreadLocal
      • In multithreading, the data between each thread is isolated from each other

4, ThreadLocal internal structure

  • Each Thread maintains a ThreadLocal Map. The key of this Map is the ThreadLocal instance itself, and the value is the real value to be stored

  • Illustration

    A Map consists of an Entry key value pair

    After the Thread is destroyed, the corresponding ThreadLocalMap will also be destroyed, which can reduce the use of memory

5, ThreadLocal core method source code

1. set method

  • Source code and corresponding Chinese Notes

    /**
      * The parameter is the value in the Map
      */
    public void set(T value) {
    
        // Gets the current thread object
        Thread t = Thread.currentThread();
    
        // Gets the ThreadLocalMap object maintained in this thread object
        ThreadLocalMap map = getMap(t);
    
        // Determine whether the map exists
        if (map != null)
            // If it exists, call the set method. This indicates the TheadLocal instance in this thread
            // Create Entry and assign value
            map.set(this, value);
        else
            // If the ThreadLocalMap object does not exist in the current thread, a Map is created
            createMap(t, value);
    }
    
    /**
      * Called getMap method
      * @param Current thread
      * @return Corresponding maintained ThreadLocalMap 
      */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    /**
      * Called createMap method
      * @param Current thread
      * @param The value of the first entry stored in the Map
      */
    void createMap(Thread t, T firstValue) {
        //this is a ThreadLocal instance
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
  • Code execution process

    • First, get the current thread and get a Map according to the current thread
    • If the obtained Map is not empty, set the parameter to the Map (the reference of the current ThreadLocal is used as the key), create an Entry and assign a value
    • If the Map is empty, create a Map for the thread and set the initial value, which is the parameter value

2. get method

  • Source code and corresponding Chinese Notes

    /**
      * Returns the value in the Map of the current thread
      */
    public T get() {
    
        // Gets the current thread object
        Thread t = Thread.currentThread();
    
        // Gets the ThreadLocalMap object maintained in this thread object
        ThreadLocalMap map = getMap(t);
    
        // If this map exists
        if (map != null) {
    
            // With the current ThreadLocal as the key, call getEntry to get the corresponding Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
    
            // If the Entry is not empty, the corresponding value is returned
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
    
        //There are two cases where the setInitialValue() method is executed
        //The first case: the map does not exist, which means that this thread does not maintain the ThreadLocalMap object
        //The second case: the map exists, but there is no entry associated with the current ThreadLocal
        return setInitialValue();
    }
    
    /**
      * The called setInitialValue method creates an Entry and returns the corresponding value value, which is null by default
      */
    private T setInitialValue() {
    
        // Call initialValue to get the initialized value
        // This method can be overridden by subclasses. If it is not overridden, it returns null by default
        T value = initialValue();
    
        // Gets the current thread object
        Thread t = Thread.currentThread();
    
        // Gets the ThreadLocalMap object maintained in this thread object
        ThreadLocalMap map = getMap(t);
    
        // Determine whether the map exists
        if (map != null)
            // Map. Is called if it exists Set set this entity entry
            map.set(this, value);
        else
            // 1. The ThreadLocalMap object does not exist in the current Thread
            // 2. Call createMap to initialize the ThreadLocalMap object
            // 3. Store t (current thread) and value (null by default) as the first entry in ThreadLocalMap
            createMap(t, value);
    
        // Returns the set value, which is null by default
        return value;
    }
    
    
  • Code execution process

    • First, get the current thread and get a Map according to the current thread
    • If the obtained Map is not empty, obtain the corresponding entry in the Map with the reference of ThreadLocal as the key e
    • If e is not empty, e.value is returned
    • If the Map is empty or e is empty, obtain the initial value value (null by default) through the initialValue() function, and then create a new Map with the reference and value of ThreadLocal as the firstKey and firstValue
  • Summary: first get the ThreadLocalMap variable of the current thread. If it exists, return the value. If it does not exist, create and return the initial value.

6, Use of weak references

The source code corresponding to Entry is as follows:

//Entry inherits from a weak reference
static class Entry extends WeakReference<ThreadLocal<?>> {

    //Attribute value
    Object value;

    //constructor 
    Entry(ThreadLocal<?> k, Object v) {
        super(k); //The parent class will create a weak reference, and the parameter is ThreadLocal object
        value = v;
    }
}

//Called parent method
public WeakReference(T referent) {
    super(referent);
}

//For the called parent class method, the second parameter refers to the queue and passes a null value
Reference(T referent, ReferenceQueue<? super T> queue) {
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

According to the above source code, when creating an Entry, a weak reference will be created and point to the ThreadLocal object of the current thread, as shown in the following figure:

1. Why use weak references?

If the key in the Entry uses a strong reference to point to the ThreadLocal object, when ThreadLocal instance threadLocal = null; Because the ThreadLocal object is still strongly referenced by the key in the Entry, the ThreadLocal object will not be garbage collected and there is a memory leak.

Memory leak: the object will not be reclaimed by GC, but it occupies memory.

If weak references are used, the ThreadLocal object will be recycled during garbage collection, which can solve the problem of memory leakage of ThreadLocal object.

2. Is there still a memory leak after using weak references?

After the ThreadLocal object is recycled, the key in the Entry is null, so the value cannot be obtained and cannot be recycled. Therefore, there is still a memory leak in the memory space pointed to by value, so you should use the remove() method to delete this Entry record.

3. The thread pool return thread must clean up the Map

If the ThreadLocalMap is not cleared when the thread in the thread pool is returned, the data in the Map may be wrong when the thread is used again. For example, if a key with the same name is found in the Map, no new value will be inserted.

Topics: Java jvm Redis Multithreading Concurrent Programming