[Multithreading] What is ThreadLocal? What are the usage scenarios? What should I pay attention to when using ThreadLocal?

Preface

1. What is ThreadLocal?

ThreadLocal is thread local storage. A ThreadLocalMap object is created in each thread. Each thread can access the value in its own internal ThreadLocalMap object.

2. What are the usage scenarios?

The classic usage scenario is to allocate a JDBC connection Connection to each thread. This ensures that each thread performs database operations on its own Connection, and there will be no problems such as thread A closing the Connection being used by thread B; or issues such as session management.

ThreadLocal usage example:

public class TestThreadLocal {
    
    
	
	//线程本地存储变量
	private static final ThreadLocal<Integer> THREAD_LOCAL_NUM = new ThreadLocal<Integer>() {
    
    
		@Override
		protected Integer initialValue() {
    
    
			return 0;
		}
	};
 
	public static void main(String[] args) {
    
    
		for (int i = 0; i < 3; i++) {
    
    //启动三个线程
			Thread t = new Thread() {
    
    
				@Override
				public void run() {
    
    
					add10ByThreadLocal();
				}
			};
			t.start();
		}
	}
	
	/**
	 * 线程本地存储变量加 5
	 */
	private static void add10ByThreadLocal() {
    
    
		for (int i = 0; i < 5; i++) {
    
    
			Integer n = THREAD_LOCAL_NUM.get();
			n += 1;
			THREAD_LOCAL_NUM.set(n);
			System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
		}
	}
	
}

Printing results: 3 threads are started, and each thread finally prints "ThreadLocal num=5" instead of num which keeps accumulating until the value is equal to 15

Thread-0 : ThreadLocal num=1
Thread-1 : ThreadLocal num=1
Thread-0 : ThreadLocal num=2
Thread-0 : ThreadLocal num=3
Thread-1 : ThreadLocal num=2
Thread-2 : ThreadLocal num=1
Thread-0 : ThreadLocal num=4
Thread-2 : ThreadLocal num=2
Thread-1 : ThreadLocal num=3
Thread-1 : ThreadLocal num=4
Thread-2 : ThreadLocal num=3
Thread-0 : ThreadLocal num=5
Thread-2 : ThreadLocal num=4
Thread-2 : ThreadLocal num=5
Thread-1 : ThreadLocal num=5

3. Implementation Principle

According to our first intuition, we feel that there must be a Map structure inside ThreadLocal. The key stores Thread and the value stores the value of local variable V. Each time, the local variables stored in the current thread are obtained and set through the get() and set(T value) methods of the ThreadLocal object.

Insert image description here

In the JDK implementation, this Map belongs to Thread, not ThreadLocal. ThreadLocal is just a proxy tool class and does not hold any thread-related data internally. All thread-related data is stored in Thread. It is also more reasonable for ThreadLocalMap to belong to Thread.

There is a deeper reason, such a design is not prone to memory leaks.
The Map held by ThreadLocal will hold a reference to the Thread object. As long as the ThreadLocal object exists, the Thread object in the Map will never be recycled. The life cycle of ThreadLocal is often longer than that of threads, so this design scheme can easily lead to memory leaks.

In the JDK implementation, Thread holds ThreadLocalMap, and the reference to ThreadLocal in ThreadLocalMap is still a weak reference (WeakReference), so as long as the Thread object can be recycled, the ThreadLocalMap can be recycled. This implementation of the JDK is complex but more secure.

Insert image description here

4. Why might using ThreadLocal in a thread pool lead to memory leaks?

The survival time of threads in the thread pool is too long, and they often live and die with the program. In this way, the ThreadLocalMap held by Thread will never be recycled. In addition, the Entry in ThreadLocalMap has a weak reference to ThreadLocal (WeakReference). , so as long as ThreadLocal ends its life cycle, it can be recycled.
The Value in Entry is strongly referenced by Entry. Even if the value's life cycle ends, the value cannot be recycled, causing memory leaks.

5. How to use ThreadLocal correctly in the thread pool?

Manually clean up the value in ThreadLocal in the finally code block and call the remove() method of ThreadLocal.

Six, ThreadLocal core method

  • Set the Value value corresponding to Thread, create a ThreadLocalMap for the first time, add ThreadLocal - Value to ThreadLocalMap, and bind ThreadLocalMap to the current thread.
public void set(T value) {
    
    
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null)
		map.set(this, value);
	else
		createMap(t, value);
}
 
  • Create a ThreadLocalMap, bound to the current thread.
void createMap(Thread t, T firstValue) {
    
    
	t.threadLocals = new ThreadLocalMap(this, firstValue);
}
 
  • Get the stored Value of the current thread through ThreadLocalMap
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();
}
 
  • Set the initialization value of ThreadLocal. If set(T value) is not called, it will be called when the Value value corresponding to Thread is obtained for the first time, that is, it will be called by the setInitialValue method. This method needs to be overridden.
protected T initialValue() {
    
    
	return null;
}
  • Remove the Value value stored by the current thread. When ThreadLocal is no longer in use, it is best to call the remove() method in the finally statement block to release the reference to Value to avoid memory leaks.
public void remove() {
    
    
	ThreadLocalMap m = getMap(Thread.currentThread());
	if (m != null)
		m.remove(this);
}

Guess you like

Origin blog.csdn.net/u011397981/article/details/132757499