Purpose of ThreadLocal
Two usage scenarios:
1. Typical scenario 1: Each thread needs an exclusive object (usually a tool class, the typical classes to be used are SimpleDateFormat and Random)
2. Typical scenario 2: Global variables need to be saved in each thread (for example, user information is obtained in the interceptor), which can be used directly by different methods to avoid the trouble of parameter passing.
Each thread needs an exclusive object
Each Thread has its own instance copy, not shared
Analogy: There is only one textbook for a class. There is a thread safety problem (concurrent reading and writing) when taking notes together. It is no problem for everyone to copy one.
1. Evolution of SimpleDateFormat
2 threads print time
/** * 2 threads print date */ public class ThreadLocalNormalUsage00 { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { String date = new ThreadLocalNormalUsage00().date(10); System.out.println(date); } }).start(); new Thread(new Runnable() { @Override public void run() { String date = new ThreadLocalNormalUsage00().date(1007); System.out.println(date); } }).start(); } public String date(int seconds) { //The unit of the parameter is 1970.1.1 00:00:00 GMT time Date date = new Date(1000 * seconds); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); return dateFormat.format(date); } }
30 threads print time
/** * ,10 threads print date */ public class ThreadLocalNormalUsage01 { public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 30; i++) { int finalI =i; new Thread(new Runnable() { @Override public void run() { String date = new ThreadLocalNormalUsage01().date(finalI); System.out.println(date); } }).start(); Thread.sleep(100); } } public String date(int seconds) { //The unit of the parameter is 1970.1.1 00:00:00 GMT time Date date = new Date(1000 * seconds); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); return dateFormat.format(date); } }
When the demand becomes 1000, then the thread pool must be used, otherwise it will consume too much memory
Thread safety issues occur when all threads share a SimpleDateFormat object
** * ,1000 The task of printing the date of each thread is executed by the thread pool */ public class ThreadLocalNormalUsage03 { private static ExecutorService threadLocal = Executors.newFixedThreadPool(10); static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { int finalI = i; threadLocal.submit(new Runnable() { @Override public void run() { String date = new ThreadLocalNormalUsage03().date(finalI); System.out.println(date); } }); } threadLocal.shutdown(); } public String date(int seconds) { //The unit of the parameter is 1970.1.1 00:00:00 GMT time Date date = new Date(1000 * seconds); return dateFormat.format(date); } }
The print results appear at the same time
Use locking to solve thread safety problems and directly lock the format method
public String date(int seconds) { //The unit of the parameter is 1970.1.1 00:00:00 GMT time Date date = new Date(1000 * seconds); String s = null; synchronized (ThreadLocalNormalUsage04.class) { s = dateFormat.format(date); } return s; }
Although locking can solve the problem, it is inefficient in the case of high concurrency. At this time, using ThreadLocal can solve the performance problem
/** * Description: Using ThreadLocal, assign each thread its own dateFormat object to ensure thread safety and efficient use of memory */ public class ThreadLocalNormalUsage05 { private static ExecutorService threadLocal = Executors.newFixedThreadPool(10); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { int finalI = i; threadLocal.submit(new Runnable() { @Override public void run() { String date = new ThreadLocalNormalUsage05().date(finalI); System.out.println(date); } }); } threadLocal.shutdown(); } public String date(int seconds) { //The unit of the parameter is 1970.1.1 00:00:00 GMT time Date date = new Date(1000 * seconds); // SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get(); return dateFormat.format(date); } } class ThreadSafeFormatter { public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); } }; //lambda expression writing public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal.withInitial( () -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss") ); }
Global variables need to be saved in each thread
A more cumbersome 2 solution is to pass the user as a parameter layer by layer, from service-1(), to service-2(), and then from service-2() to service-3(), and so on, but Doing so results in redundant and less maintainable code
1. Use ThreadLocal to save some business content (user permission information, user name obtained from the user system, userID, etc.)
2. The information is the same in the same thread, but the business content used by different threads is different
3. In the thread life cycle, the get() method of this static ThreadLocal instance is used to obtain which object you have set, avoiding the trouble of passing this object (such as the user object) as a parameter
4. The emphasis is on sharing between different methods within the same request (within the same thread).
5. There is no need to override the initialValue() method, but you must manually call the set() method
Example: The current information needs to be shared by all methods in the thread
Evolve on the previous basis, using UserMap
When multiple threads work at the same time, we need to ensure thread safety. We can use synchronized or ConcurrentHashMap, but no matter what we use, it will affect performance.
A better way is to use ThreadLocal, which does not require synchronization, and can achieve the purpose of saving user information corresponding to the current thread without affecting performance and without passing parameters layer by layer.
/** * Description: Demonstrate ThreadLocal usage 2: Avoid the hassle of passing parameters */ public class ThreadLocalNormalUsage06 { public static void main(String[] args) { new Service1().process(); } } class Service1 { public void process() { User user = new User("super brother"); UserContextHolder.holder.set(user); new Service2().process(); } } class Service2 { public void process() { User user = UserContextHolder.holder.get(); System.out.println("service2 get username: "+user.name); new Service3().process(); } } class Service3 { public void process() { User user = UserContextHolder.holder.get(); System.out.println("service3 get username "+user.name); } } class UserContextHolder { public static ThreadLocal<User> holder = new ThreadLocal<>(); } class User { String name; public User(String name) { this.name = name; } }
ThreadLocal two roles
1. Isolate an object that needs to be used between threads (each thread has its own independent object)
2. The object can be easily obtained in any method
Two different implementations:
Depending on the generation timing of the shared object, select initialValue or set to save the object
1. Scenario 1 (initialValue): Initialize the object when ThreadLocal get s the first time, and the initialization timing of the object can be controlled by us
2 Scenario 2 (set): If the generation timing of the objects that need to be saved in ThreadLocal is not controlled by us at will, such as the user information generated by the interceptor, use ThreadLocal.set() to directly put it into our ThreadLocal for subsequent use.
Benefits of using ThreadLocal
1. Reach thread safety
2. No need to lock, improve execution efficiency
3. Use memory more efficiently and save overhead: Compared to creating a new SimpleDateFormat for each task, obviously using ThreadLocal can save memory and overhead
4. Eliminate the tediousness of passing parameters: Whether it is the tool class of scene 1 or the user name of scene 2, it can be obtained directly through ThreadLocal anywhere, and it is no longer necessary to pass the same parameters every time. ThreadLocal makes code less coupled and more elegant.
ThreadLocal principle, source code analysis
Understand the relationship between Thread, ThreadLocal and ThreadLocalMap
1. Each Thread object holds a ThreadLocalMap member variable
Member variable ThreadLocalMap in Thread
ThreadLocal.ThreadLocalMap threadLocals = null;
2. Introduction of main methods
initialValue():
① This method will return the "initial value" corresponding to the current thread. This is a lazy loading method, which will only be triggered when get() is called.
② When the thread uses the get() method variable for the first time, the initialValue() method will be called, unless the thread has previously called the set() method, in which case the initialValue() method will not be called for the thread
③ Usually, each thread calls the initialValue() method at most once, but if you have called remove and then call get(), you can call this method again
④ If you do not override the initialValue() method, this method will return null. Generally, the method of the anonymous inner class is used to override the initialValue() method, so that the copy object can be initialized in subsequent use.
The initialValue() method returns null by default, so we override the initialValue() method
protected T initialValue() { return null; }
void set(T t): set a new value for this 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); }
T get(): Get the value corresponding to this thread. If get() is called for the first time, initialize will be called to get the value. The get() method is to first take out the ThreadLocalMap of the current thread, and then call the map.getEntry() method. Pass in the reference of ThreadLocal as a parameter, and take out the Value belonging to this ThreadLocal in the Map
Note: This map and the key and value in the map are stored in the thread, not in ThreadLocal
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);//Get the member variable ThreadLocalMap in Thread if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
If map is equal to null, call setInitialValue(),setInitialValue() calls initialValue()
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; }
void remove(): delete the value corresponding to this thread
class Service2 { public void process() { User user = UserContextHolder.holder.get(); System.out.println("service2 get username: " + user.name); UserContextHolder.holder.remove(); user = new User("Zhang San"); UserContextHolder.holder.set(user); new Service3().process(); }
remove() source code
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
ThreadLocalMap class
1.ThreadLocalMap class, which is Thread.ThreadLocals
2. The ThreadLocalMap class is a variable in the Thread class of each thread. The most important thing in it is an array of key-value pairs, Entry[] table, which can be considered as a map.
key-value pair:
key: this ThreadLocal
Value: member variables that are actually required, such as user or simpleDateFormat objects
Similar to HashMap, but there are differences, the way to resolve conflicts:
HashMap zipper method:
ThreadLocalMap uses a linear detection method, that is, if there is a conflict, it will continue to find the next empty position, instead of using a linked list zipper
Similarities between the two usage scenarios
1. It can be seen from the source code analysis that setInitialValue and direct set finally use the map.set() method to set the value
2. In other words, it will eventually correspond to an Entry of ThreadLocalMap, but the starting point and entry are different
ThreadLocal causes memory leak
Memory leak: An object is no longer useful, but the memory it occupies cannot be reclaimed
1. Leak of Key: Entry in ThreadLocalMap inherits from WeakReference, which is a weak reference
static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
2. Characteristics of weak references: If the object is only associated with weak references (without any strong reference associations), then the object can be recycled, and weak references will not prevent GC
Value leakage: Each Entry of ThreadLocalMap is a weak reference to key, and each value contains a strong reference to value
Under normal circumstances, when the thread terminates, the value stored in ThreadLocal will be garbage collected because there are no strong references.
However, if the thread does not terminate (for example, the thread needs to be kept for a long time), then the value corresponding to the key cannot be recycled. For example, when the thread pool is used, the same thread is used repeatedly, because there is the following call chain:
Because there is still this strong reference link between value and Thread, vaLue cannot be recycled, and OOM may occur
JDK has taken this problem into account, so in the set, remove, and rehash methods, it will scan the Entry whose key is null, and set the corresponding value to null, so that the value object can be recycled
private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; if (e != null) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; // Help the GC } else { int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; }
However, if a ThreadLoca is not used, then the set, remove, and rehash methods will not be called. If the thread does not stop at the same time, the call chain will always exist, which will lead to a memory leak of value.
How to avoid memory leaks
Calling the remove() method will delete the corresponding Entry object, which can avoid memory leaks, so after using ThreadLocal, you should call the remove method
ThreadLocal null pointer problem
The object of the set wrapper class
ThreadLocal<Long> longThreadLocal = new ThreadLocal<>();
If get returns a primitive type, an error null pointer exception is reported
Originally, get is null before it is assigned. When the object type is converted to a basic type, it cannot be converted, resulting in a null pointer exception.
ThreadLocal Notes
1. Before performing get, you must set first, otherwise a null pointer exception will be reported? So it's not, it's just a problem of boxing and unboxing instead of ThreadLocal
2. Shared object: If the thing in ThreadLocal.set() in each thread is the same object shared by multiple threads, such as a sttic object, then the shared object itself is obtained by ThreadLocal.get() of multiple threads , there are still concurrent access problems, so static objects should not be placed in ThreadLocal
3. If the problem can be solved without using ThreadLocal, then do not use it forcibly. For example, when the number of tasks is small, the problem can be solved by creating a new object in the local variable, then you do not need to use ThreadLocal
4. Prioritize the support of the framework instead of creating it yourself. For example, in Spring, if you can use RequestContextHolder, you don't need to maintain ThreadLocal yourself, because you may forget to call the remove method, etc., causing memory leaks
ThreadLocal practical application scenarios
Instance Analysis in Spring
1.DateTimeContextHolder class, which uses the context of ThreadLocal storage time
2. Each Http request corresponds to a thread, and the threads are isolated from each other. This is the typical application scenario of ThreadLocal