ThreadLocal is no stranger to you, everyone has probably used it or touched it at some point, so do you really know it? I just unveiled her today.
After reading this article, you will GET the following knowledge:
- What is ThreadLocal?
- Does ThreadLocal really cause memory overruns?
- ThreadLocal
- ThreadLocal best practices
- What problem does ThreadLocal.remove solve?
What is a ThreadLocal?
ThreadLocal literally stands for thread-local variables, so what are thread-local variables? What problem did he solve? Let’s look at this example
public class ThreadLocalTest {
public static void main(String[] args) {
Task task = new Task();
for (int i = 0; i < 3; i++) {
new Thread(() -> System.out.println(Thread.currentThread().getName() + ":" + task.calc(10))).start();
} } static class Task { private int value; public int calc(int i) { value += i; return value; } } } Copy the code
Start three threads, call the calc method, and print the name of the thread and the contents of the calculation. Output is as follows:
Thread-0 : 10
Thread-1 : 20
Thread-2 : 30
Copy the code
The result is not difficult to analyze, because the three threads share the same Task, so the value content will accumulate, so the result is not what you expected? So let’s do another example
public class ThreadLocalTest2 {
public static void main(String[] args) {
ThreadLocalTest2.Task task = new ThreadLocalTest2.Task();
for (int i = 0; i < 3; i++) {
new Thread(() -> System.out.println(Thread.currentThread().getName() + ":" + task.calc(10))).start();
} } static class Task { ThreadLocal<Integer> value; public int calc(int i) { value = new ThreadLocal(); value.set((value.get() == null ? 0 : value.get()) + i); return value.get(); } } } Copy the code
The result is as follows
Thread-0 : 10
Thread-1 : 10
Thread-2 : 10
Copy the code
Change the value to ThreadLocal so that each thread does not affect the content of each other, so why can it do that? That’s what ThreadLocal is all about. It takes care of thread-private variables. Threads don’t interact. Let’s take a look at the source code
ThreadLocal source code appreciation
The simplest way to look at the source code is from the entry. The threadlocal. set method gets the current thread directly and then calls getMap(t), which is the threadLocals variable of the current thread. ThreadLocal is a utility class that allows you to set the contents of the current thread in k-V mode (using ThreadLocalMap). There is no thread-safety problem since it is stored on the current thread. This is the content of thread-local variables.
It’s important to note, of course, that the key is this, which is the current ThreadLocal object, as we’ll see below.
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else createMap(t, value); } Copy the code
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Copy the code
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code
Does ThreadLocal run out of memory?
That’s not the end of the story. One of the most talked about problems with ThreadLocal is its memory running out.
Let’s take a look at another example that uses only one thread (the Main thread) to loop through the example, creating a new Task each time, as we can imagine, so that each new Task is created, as long as the thread does not terminate. If the loop is large enough and the thread is always present, it will run out of memory. We’ll have to try it ourselves.
In the following example, we do a forced GC at I == 80. Let’s DEBUG to see the effect.
public class ThreadLocalTest3 {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Task().calc(10);
if (i == 80) {
System.gc(); } } } static class Task { ThreadLocal<Integer> value; public int calc(int i) { value = new ThreadLocal(); value.set((value.get() == null ? 0 : value.get()) + i); return value.get(); } } } Copy the code
Click Debug on the left side of the for loop, then right-click and set the conditions as shown below, so that debug stays in the loop where I equals 79 and 81. Loop 100 times to get a better view. Ok, so now we can look directly at what happens when I == 80
i == 79 || i == 81
Copy the code
So to start my show, the DEBUG stops at 79 and 81, respectively. Let’s run the current thread to get the contents of threadLocals
Thread.currentThread().threadLocals
Copy the code
You can see that the size of the ThreadLocalMap in the two images is 83 and 4 respectively. Did GC collect 83-4 = 79 ThreadLocalMap contents?
Okay, so let’s move on to the code
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if(e ! =null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if(key ! =null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while(table[h] ! =null) h = nextIndex(h, len); table[h] = c; size++; } } } } Copy the code
static class Entry extends WeakReference<ThreadLocal<? >>{
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<? > k, Object v) { super(k); value = v; } } Copy the code
Each Entry stored in the original ThreadLocalMap of ThreadLocal is a WeakReference. WeakReference will be recovered during GC, and what is recovered is actually key, which is the referent of WeakReference. ThreadLocal will then delete values with empty keys on set and GET, thus eliminating the memory overflow caused by different ThreadLocal’s being continuously appended to the current thread during its lifetime.
Wait, if I write my own program, I won’t be able to get ThreadLocal objects when I encounter GC. No, because an object can only be reclaimed if it is only referenced by WeakReference.
Well, in my excitement I drew a picture to illustrate the problem. If the reference to work1 is missing, and Entry’s reference to ThreadLocal is weak, it will be reclaimed.
Therefore, WeakReference solves the problem of memory overflow. If the holding ThreadLocal object is reclaimed, the memory will naturally be reclaimed. If the ThreadLocal object has always existed and not been reclaimed, it cannot be called memory overflow.
ThreadLocal best practices
ThreadLocal is a feature popular with frameworks such as MyBatis, which uses ThreadLocal a lot. Here is a common example. First, I have an interceptor. I use the current SL content + 10 for simulation purposes, usually this is used to pass the current login state so that it can be easily retrieved from anywhere on a single request.
public class SessionInterceptor implements HandlerInterceptor {
public static ThreadLocal<Integer> sl = new ThreadLocal();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
Integer value = sl.get(); if(value ! =null) { sl.set(value + 10); } else { sl.set(10); } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } } Copy the code
Then I fetch the contents of ThreadLocal in the Controller and print out the name and contents of the current thread
@RestController
public class IndexController {
@RequestMapping("/")
public Integer test(a) {
System.out.println(Thread.currentThread().getName() + ":" + SessionInterceptor.sl.get());
return SessionInterceptor.sl.get(); } } Copy the code
Next we start the service and run the Spring Boot Application that I wrote
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MainBootstrap {
public static void main(String[] args) {
SpringApplication.run(MainBootstrap.class, args);
}
} Copy the code
Visit https://localhost:8080 and refresh the browser crazily. The console prints the following results
http-nio-8080-exec-1 : 10
http-nio-8080-exec-3 : 10
http-nio-8080-exec-4 : 10
http-nio-8080-exec-1 : 20
http-nio-8080-exec-2 : 10
http-nio-8080-exec-3 : 20
http-nio-8080-exec-4 : 20
http-nio-8080-exec-1 : 30
http-nio-8080-exec-2 : 20
http-nio-8080-exec-3 : 30
http-nio-8080-exec-4 : 30
Copy the code
ThreadLocal = ThreadLocal; ThreadLocal = ThreadLocal; ThreadLocal = ThreadLocal; ThreadLocal = ThreadLocal; ThreadLocal = ThreadLocal;
In the days when pooling was popular, Tomcat also used pooling as a basis. In fact, each request was directly fetched from Tomcat’s thread pool, and then run, so if the contents of a ThreadLocal are not reset at the end of a request, other requests will be affected. Imagine if your place is to do user login binding, then wouldn’t it be a mess of resources?
So what’s the solution? In the SessionInterceptor class, add sl.remove() to afterCompletion, which removes the private variables from the current thread at the end of the request so that other threads are not affected.
Some people on the web say that this operation is for better GC collection of unused instances, if not set to automatic collection, which is not true. In order to make the contents of a ThreadLocal accessible to all contexts, for example, a static ThreadLocal holds references that are never reclaimed, so it restores the state of the thread and does not affect other requests.
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
sl.remove();
}
Copy the code
After modification, we re – brush the browser, is the problem solved? Well, if you have any questions about ThreadLocal, please leave me a comment for further discussion, and please point out anything that’s not right. All of the code in the article can be retrieved by replying to “ThreadLocal” behind the subscription number.
This article is formatted using MDNICE