preface
Recently, many friends have always asked me privately, what is ThreadLocal
So today we'll focus on ThreadLocal. If you think it's good after reading it, don't forget to praise it!
text
ThreadLocal is mainly used to provide thread local variables, that is, this variable is only visible to the current thread.
ThreadLocal maintains a data structure inside it (similar to HashMap). When a variable needs to be used, it will create a variable copy in each thread, and then operate the copy through set() and get(), so as to realize the data isolation between threads.
Me: it doesn't sound very difficult! Where do we usually use ThreadLocal?
Although it sounds very simple, it's not so simple to really master it. Pour a cup of tea, order a compliment, and listen to me carefully. Here are some common cases.
1) SimpleDateFormat time format conversion:
I'm sure that 100% of the partners have used SimpleDateFormat to format the time. Generally, we will package a tool class. The following example is used to print the format time of 1 to 1000 seconds.
public class ThreadLocalDemo { //Create a fixed size thread pool public static ExecutorService executorService = Executors.newFixedThreadPool(5); public static void main(String[] args) { for (int i = 0; i <= 1000; i++) { int time = i ; executorService.submit(new Runnable() { @Override public void run() { //Call time class static method String formatTime = TimeUtil.getSecond(time); System.out.println(formatTime); } }); } executorService.shutdown(); } } //Time tool class class TimeUtil { static SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-DD HH:mm:ss"); public static String getSecond(int count){ Date date = new Date(1000*count); return sdf.format(date); } }
submit: add thread task; shutdown: stop the thread pool;
A simple understanding of thread pool here is to always maintain several threads running. When a task arrives, it will be processed immediately. If there are too many tasks to be processed, it will be stuffed into the queue and continue to be executed after the task being executed is completed.
At this time, we will find a strange scene. The time variable is clearly a natural number from 0 to 1000, and there will be no duplicate numbers. Why will the same formatting result be printed here. Do you think of those methods written before and feel cold on your back.
The reason for this is that all threads share the same simpleDateFormat object, which leads to thread safety problems. Smart little partners can certainly think that we can lock the key code with synchronized to ensure the thread safety of the result.
public static String getSecond(int count){ Date date = new Date(1000*count); String result = null; synchronized(ThreadLocalDemo.class){ result = sdf.format(date); } return result; }
In this way, our purpose can be achieved, and there will be no accidents in the results, but doing so will lead to only one thread executing time formatting (execution serialization) at the same time, which will seriously affect the performance of the program and cannot be tolerated in the case of high concurrency.
At this time, our protagonist ThreadLocal will appear on the stage. Here, look at the code directly. The main method remains unchanged and only the tool class is modified.
class TimeUtil { public static String getSecond(int count){ Date date = new Date(1000*count); //Get SimpleDateFormat using get SimpleDateFormat sdf = ThreadLocalUtils.simpleDateFormatThreadLocal.get(); return sdf.format(date); } } //Create ThreadLocal class ThreadLocalUtils { public static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>(){ @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("YYYY-MM-DD HH:mm:ss"); } }; }
At this time, no matter how it is executed, the same result will not appear, indicating that the object created with ThreaLocal can ensure the thread.
2) Avoid passing some parameters
618 I don't know if you have cut your hands. Anyway, I have no hands to cut. I just want to live well. Here we think about a question, when you place an order, how does the background program deal with it? In fact, the background program needs to deal with many processes, including user information query, coupon query, receiving address query, message notification and so on. Because the user information may be used in each step. If the user information is passed layer by layer as parameters, it will lead to high code coupling and very bloated, which is not conducive to maintenance.
Some little friends may think that I don't need your ThreadLocal. I write a static map collection for storage. Can't I save it?
When multiple threads access the same variable, we know that there will be thread safety problems. If you use a collection of thread safety types (such as ConCurrentHashMap) or lock directly, it will affect the execution performance of the program, which is the same as the performance problems caused by the use of synchronized code block modification above.
Therefore, we can conclude that during the life cycle of a thread, the set() method of ThreadLocal can be used to store the private variable of the thread and obtain it through get() when the variable is needed. The content of this variable is independent in different threads, which avoids the trouble of multi-level parameter transmission without losing performance.
public class ThreadLocalDemo2 { public static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void main(String[] args) { User user = new User("flower Gie"); ThreadLocalInfo.userThreadLocal.set(user); //1. Call the method of obtaining address new AddressService().getAddress(); } } class AddressService{ public void getAddress(){ User user = ThreadLocalInfo.userThreadLocal.get(); System.out.println("According to user information"+user.getUserName()+"Get user address"); //2. Call coupon method new TicketService().getTicket(); } } class TicketService{ public void getTicket(){ User user = ThreadLocalInfo.userThreadLocal.get(); System.out.println("According to user information"+user.getUserName()+"Get user coupons"); //3. Call send message new MessageService().sendMessage(); } } class MessageService{ public void sendMessage(){ User user = ThreadLocalInfo.userThreadLocal.get(); System.out.println("According to user"+user.getUserName()+"send message"); } } class User { String userName; public User(String userName) { this.userName = userName; } public String getUserName() { return userName; } } //Create ThreadLocal variable class ThreadLocalInfo { public static ThreadLocal<User> userThreadLocal = new ThreadLocal<>(); }
The explanation of the code is really fragrant and clear. In the three steps of obtaining the address, obtaining the coupon and sending the message, we do not transfer the user object layer by layer, but we can get the user object content. Even if the subsequent requirements or processes are adjusted, we can easily deal with it.
Me: now I know the usage and function. Can you tell me the implementation principle?
OK, let's take a look at this picture first to understand the relationship among Thread, ThreadLocal and ThreadLocalMap.
It can be clearly seen from the figure that each Thread will have a ThreadLocalMap object, and each ThreadLocalMap contains multiple threadlocales.
Me: This is your own drawing... How do I know if it's bullshit!
It took Gie a few days to be smart. Let's take a look at the source code. ThreadLocal has four important methods:
public T get() {} public void set(T value) {} protected T initialValue() {}
First, let's take a look at the initialValue() method we rewritten in the first scenario:
protected T initialValue() { return null; }
Obviously, if we don't actively override the initialValue() method, it will return a null value.
Next, let's look at the set method:
public void set(T value) { //1. Get the current thread Thread t = Thread.currentThread(); //2. Get ThreadLocalMap object ThreadLocalMap map = getMap(t); //3. If the map exists, the current ThreadLocal object will be stored in the map as a key //this: is the current ThreadLocal if (map != null) map.set(this, value); else //4. When the map does not exist, it is created createMap(t, value); }
The second step above has a ThreadLocalMap. What is this? In fact, we can find the result in the Thread class, which is an internal class in the Thread. Here's the key code. ThreadLocalMap uses the key value array Entry[] table to store data. Entry can be compared to a map, where the key refers to ThreadLocal; Value is the content to be saved, such as SimpleDateFormat and user.
ThreadLocalMap is the gray part in the figure above
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private Entry[] table; ....ellipsis }
Then look at the contents of getMap. It will return the threadLocals object of the current thread. Therefore, when initialValue() is not overridden for initialization, the first call to set() method getMap will return a null, and then createMap it.
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } // Thread.java // threadLocals defaults to null ThreadLocal.ThreadLocalMap threadLocals = null;
The createMap operation is also relatively simple. It is used to create a ThreadLocalMap object and assign it to the threadLocals variable of the current thread.
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
After reading the above source code, the get() method is very easy to understand. First look at the code in step 3. If the set() method has been called before (the threadLocals object has not been initialized), the current ThreadLocal object will be used as the key to obtain the value value saved in threadLocals in advance.
public T get() { //1. Get the current thread Thread t = Thread.currentThread(); //2. Get the threadLocalMap of the current thread ThreadLocalMap map = getMap(t); //3. If ThreadLocal has been initialized if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //4. If ThreadLocal is not initialized, call initialValue() to initialize return setInitialValue(); }
But what does the last sentence do? In fact, the method of delayed loading is used here. When we rewrite the initialValue() method, it does not initialize immediately. Instead, we wait until the first query to execute the setInitialValue() method for initialization.
private T setInitialValue() { //Overridden initialValue method T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
See here, do you basically understand the relationship and process of Thread, ThreadLocal and ThreadLocalMap in the figure.
Me: although it's very wordy, it's really detailed!
Here's what I want to say. Every time I say so much, my hands are sour and my mouth is tired, but I'm very worried that if I ignore some details, many little partners may not understand. Therefore, the little partners with more disadvantages can jump to see and ignore some details. If they are novice partners, they look easier and easier to understand.
Me: Gouzi is very attentive. Flower Gie likes you here. Since ThreadLocal has so many advantages, is there any defect in it?
Everything has two sides. ThreadLocal must have some problems to pay attention to, that is, memory leakage
Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; }
As in the above code, value = v means that the value we set in ThreadLocal and ThreadLocalMap are strong references. Combined with the above figure, we can get the reason for memory leakage: because the life cycle of ThreadLocalMap is as long as that of Thread, if the Thread is not destroyed, ThreadLocal will survive all the time. If the corresponding key is not deleted manually, memory leakage will occur.
For example, in the scenario of thread pool, because threads always survive, using ThreadLocal at this time will lead to memory leakage. If you want to avoid memory leakage, you need to manually remove()!
Me: dog, I'm stunned at the effect of your two-day cultivation. Let's summarize it for my friends.
ThreadLocal can be summarized as follows:
- In the life cycle of the thread, the object can be easily obtained;
- effect:
- Each thread can have its own independent object, which can be isolated from other threads;
- In the life cycle of the thread, the object can be easily obtained;
- advantage:
- Thread safety can be achieved without locking
- Efficient use of memory and cost savings
- There is no need to transfer parameters layer by layer to realize code decoupling
- Scene selection:
- For the tool class, when all threads share one instance, the ThreadLocal variable is initialized when it is created;
- When different threads have different objects, set() method can be used flexibly.
- Principle summary:
- ThreadLocalMap is the internal class of Thread, and each Thread maintains a reference to ThreadLocalMap
- initialValue() and set() set key value pairs are essentially the same, and both of them call map set(this,value)
- ThreadLocal itself does not store values, it just gets values from ThreadLocalMap as a key.
summary
The purpose of using ThreadLocal is not to solve the problem of concurrent or shared variables, but to have its own variables in the current thread and realize thread data isolation.
last
The following are the interview questions that must be asked in the development of Java interview for more than 1-5 years. They are also the necessary skills for Java interview of front-line Internet companies. The following is the skill chart required by Alibaba's annual salary of 50W. You can refer to it!
At the same time, for these 12 skills, I sorted out a special PDF document for advanced interview of Java architecture here (including 450 question analysis, including analysis of knowledge points such as Dubbo, Redis, Netty, zookeeper, Spring cloud, distributed, high concurrency, design mode, MySQL, rich content and combination of graphics and text!)
This special document is free to share. Friends in need can look below to get it!!
If you need a full version of the document, you can click three times and get the free way below!
Uh huh