Analysis of ThreadLocal

Introduce a,

The ThreadLocal class is used to provide local variables within a thread, ensuring isolation of variables within a thread in a multi-threaded environment.

In other words, ThreadLocal provides local variables within a thread that do not interfere with each other and that extend throughout the lifetime of the thread, reducing the complexity of passing some common variables between methods or components within the same thread.

Second, the use of

2.1 Common Methods

The return value The method name describe
T get() Returns the value in the current thread copy of this thread-local variable
void remove() Removes the current thread value of this thread-local variable
void set(T value) Sets the value in the current thread copy of this thread-local variable to the specified value

2.2 Case Demonstration

Need: use 3 artists each to paint a color on a canvas and print out the color they painted.

/** * canvas class */
public class Canvas {
	
	private String content;

	public String getContent(a) {
		return content;
	}

	public void setContent(String content) {
		this.content = content; }}/** ** ** /
public class Painter extends Thread {

	private String name;
	
	private Canvas canvas;
	
	private String color;
	
	public Painter(String name, Canvas canvas, String color) {
		this.name = name;
		this.canvas = canvas;
		this.color = color;
	}

	@Override
	public void run(a) {
		canvas.setContent(color);
		System.out.println(this.name + "Draw on the artboard."+ canvas.getContent()); }}/** * start class */
public class Demo {

	public static void main(String[] args) {
		
		// Create canvas
		Canvas canvas = new Canvas();
		
		Painter painter1 = new Painter("Small strong", canvas, "Red");
		Painter painter2 = new Painter("Prosperous wealth.", canvas, "Yellow");
		Painter painter3 = new Painter("The dog egg", canvas, "Blue"); painter1.start(); painter2.start(); painter3.start(); }}Copy the code

The result is as follows:

Jack Bauer draws blue on the artboard. Fortune draws yellow on the artboard. Dog Egg draws yellow on the artboardCopy the code

Obviously, in the case of multiple threads accessing the same resource (canvas), the output has concurrency issues.

There are two solutions: one is to add synchronized blocks to the run method, and the other is to use ThreadLocal to modify the Canvas type.

Since this article focuses on ThreadLocal, let’s tackle this problem in a second way.

Modify the Canvas class as follows:

public class Canvas {
	
	private ThreadLocal<String> map = new ThreadLocal();

	public String getContent(a) {
		return map.get();
	}

	public void setContent(String content) { map.set(content); }}Copy the code

Start the execution class and the result is as follows:

Jack Bauer draws red dog Egg draws blue and Prosperous wealth draws yellow on the artboardCopy the code

The output is normal.

2.3 Differences between ThreadLocal and synchronized

The name of the The principle of focus
ThreadLocal In space for time, each thread provides a copy of the variable, allowing simultaneous access without interference Resources are isolated between multiple threads
synchronized Time for space, provide only one variable, let the thread queue access Multiple threads share resources and access them synchronously

3. Internal structure of ThreadLocal

Before looking at the source code, we can try to guess what the internal structure of ThreadLocal looks like.

For example, ThreadLocal internally defines a Map container. When the set method of a ThreadLocal instance is called, the current thread name/current thread instance is used as the key and the content to be saved is used as the value. When get is invoked, the current thread name/current thread instance is used as the key to get data.

The above scheme seems to function normally, but actually there are some problems:

1) The key-value container is maintained by ThreadLocal. When the number of threads increases and the set method of ThreadLocal instance is called, the key-value container also increases, that is, the memory usage increases. 2) When a thread in a ThreadLocal instance is called by a thread in the thread pool, it is impossible to distinguish whether the thread is being recycled or not, i.e. the current thread has been removed from the thread pool before calling the ThreadLocal instance's set method. If the current get method is called, it will fetch the previous data, causing problems such as data contamination.Copy the code

So what exactly is going on inside ThreadLocal to achieve the isolation of internal variables between threads?

As shown in the figure above, a container called ThreadLocalMap is maintained internally by Thread instances. Its elements are the key of ThreadLocal instances, and its data structure holds objects as values, contrary to what we might have guessed.

The JDK implementation has two advantages over the one we envisioned earlier:

1) The Map stores fewer entries. 2) When a thread is destroyed, ThreadLocalMap is also destroyed, reducing memory usageCopy the code

4. Source code analysis

4.1 ThreadLocal source

We aimed at the commonly used set, get, remove method source code analysis.

public void set(T value) {
    // Get the current thread object
    Thread t = Thread.currentThread();
    // Get the ThreadLocalMap object maintained by the current thread object
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        // If map has entry setting
        map.set(this, value);
    else
        // If the map does not exist, the threadLocal instance helps create and bind the data
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
    
Copy the code

Set method execution flow:

1) Get the current thread object 2) Get the ThreadLocalMap object from the current thread object 3) If ThreadLocalMap object exists, 4) If a ThreadLocalMap object does not exist, create a ThreadLocalMap object for the current thread and set the input parameterCopy the code

public T get(a) {
    // Get the current thread object
    Thread t = Thread.currentThread();
    // Get the ThreadLocalMap object maintained by the current thread object
    ThreadLocalMap map = getMap(t);
    if(map ! =null) {
        // If the map is not empty, the current ThreadLocal instance is used as the key to obtain data
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            returnresult; }}// If map is empty, initialize the value, usually null
    return setInitialValue();
}

private T setInitialValue(a) {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

protected T initialValue(a) {
    return null;
}
Copy the code

Get method execution flow:

1) Get the current thread object 2) Get the ThreadLocalMap object from the current thread object 3) If ThreadLocalMap object exists, 4) If the ThreadLocalMap object does not exist, initialize the value by initialValue.Copy the code

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

Remove method execution flow:

2) If a ThreadLocalMap object exists, the current ThreadLocal instance is used as the key for data deletionCopy the code

4.2 ThreadLocalMap source

ThreadLocalMap is an internal class of ThreadLocal that implements Map functionality without the Map interface.

Member variables:

/** * initial capacity, must be a whole power of 2 */
private static final int INITIAL_CAPACITY = 16;

/** * the table where the data is stored. The data length is also 2 power */
private Entry[] table;

/** * Number of entries in the array */
private int size = 0;

/** * the threshold for expansion */
private int threshold; // Default to 0
Copy the code

Entry Inner class:

static class Entry extends WeakReference<ThreadLocal<? >>{ Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}Copy the code

Entry inherits the WeakReference class, where the key is a WeakReference, and its purpose is to decouple the life cycle of a ThreadLocal object from the life cycle of a thread.

5. Memory leaks

Although ThreadLocal is used as a weak reference key, it can still cause memory leaks in some cases. Before analyzing memory leaks, let’s add a few concepts:

Memory leak: The heap memory that has been dynamically allocated in the program is not released or cannot be released for some reason, resulting in a waste of system memory, slowing down the program, or even crashing the system. This problem eventually leads to memory overflow strong reference: The garbage collector will not collect weak references as long as there are strong references to an object, indicating that the object is still "alive" : Garbage collection period Once an object with only weak references is found, regardless of whether the current memory space is sufficient, the object will be reclaimedCopy the code

With the basic concepts in mind, let’s analyze memory leaks using ThreadLocal:

The figure above shows the memory structure of a thread using ThreacLocal, with solid arrows representing strong references and dotted arrows representing weak references.

When ThreadLocal usage ends, the ThreadLocal reference to stack memory is reclaimed, that is, the reference1No longer pointing to ThreadLocal objects. Due to the reference2Is a weak reference. There is no strong reference to a ThreadLocal object, so the ThreadLocal object will be collected by the GC, where Entry key =nullIf the Entry object is not manually deleted and the current Thread is running, there will be a strong reference chain Thread reference -> Thread object -> ThreadLocal object -> Entry object -> Value. Which is the keynullThe value block of memory can never be accessed, causing a memory leak.Copy the code

Why use a weak reference as the key of a ThreadLocalMap when it would leak memory?

If the key is null, then the value is set to null.

In other words, we forget to call the remove method while the thread using ThreadLocal is still running, and weak references are one more layer of protection than strong references. ThreadLocal objects referred to by weak references are reclaimed, and the corresponding value is set to null when TheadLocalMap calls any of the set, getEntry, or remove methods to avoid memory leaks.

Six, summarized

Use ThreadLocal to transfer common variables in different components on the same thread. Variables in each thread are independent of each otherCopy the code

Note: To prevent memory leaks and as a good development practice, it is important to call the remove method manually after using ThreadLocal.