Be sure to place the following paragraph at the beginning of the article (keep the hyperlink). Forward in this paper, the technology of the world, the original link www.jasongj.com/java/thread…

What problem does ThreadLocal solve

Since ThreadLocal supports generics, such as ThreadLocal< StringBuilder >, variables represent the ThreadLocal itself and instances represent instances of specific types, such as StringBuidler.

Inappropriate understanding

One reason for writing this article is that many blogs on the web are not clear or even wrong about the scenarios ThreadLocal applies to and the problems it solves. The following is a common introduction to ThreadLocal

ThreadLocal provides a new way to solve the concurrency problem of multithreaded programs. The purpose of ThreadLocal is to solve the problem of sharing resources when multiple threads access resources

There are many articles comparing ThreadLocal and Synchronize. Since the comparison is made, it should be assumed that the two solve the same or similar problem.

The problem with the above description is that ThreadLocal does not solve the problem of multiple threads sharing variables. Since variables are not shared, there is no question of synchronization.

Reasonable understanding

ThreadLoal (ThreadLocal< String >) ¶ ThreadLoal (ThreadLocal< String >) ¶ ThreadLoal (ThreadLocal< String >) ¶ More on that later). There are a few things to note here

  • This is because each Thread has its own instance copy that can only be used by the current Thread. That’s where ThreadLocal gets its name
  • Since each Thread has its own instance copy, and other threads cannot access it, there is no problem with multithreading sharing
  • Without sharing, how can we synchronize and how can we solve the synchronization problem?

So what problems does ThreadLocal solve, and in what scenarios?

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

The core idea is that

ThreadLocal provides thread-local instances. It differs from a normal variable in that each thread that uses it initializes a completely separate copy of the instance. ThreadLocal variables are usually decorated with private static. When a thread terminates, all copies of the relative instances of ThreadLocal used by it are recycled.

In general, ThreadLocal is suitable for scenarios where each thread needs its own independent instance and that instance needs to be used in multiple methods, that is, variables are isolated between threads and shared between methods or classes. This point of view will be elaborated through examples in the following sections. In addition, ThreadLocal is not necessary in this scenario, other methods can achieve the same effect, but ThreadLocal makes the implementation much cleaner.

ThreadLocal usage

The sample code

The following code illustrates the use of ThreadLocal

public class ThreadLocalDemo { public static void main(String[] args) throws InterruptedException { int threads = 3; CountDownLatch countDownLatch = new CountDownLatch(threads); InnerClass innerClass = new InnerClass(); for(int i = 1; i <= threads; i++) { new Thread(() -> { for(int j = 0; j < 4; j++) { innerClass.add(String.valueOf(j)); innerClass.print(); } innerClass.set("hello world"); countDownLatch.countDown(); }, "thread - " + i).start(); } countDownLatch.await(); } private static class InnerClass { public void add(String newStr) { StringBuilder str = Counter.counter.get(); Counter.counter.set(str.append(newStr)); } public void print() { System.out.printf("Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n", Thread.currentThread().getName(), Counter.counter.hashCode(), Counter.counter.get().hashCode(), Counter.counter.get().toString()); } public void set(String words) { Counter.counter.set(new StringBuilder(words)); System.out.printf("Set, Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n", Thread.currentThread().getName(), Counter.counter.hashCode(), Counter.counter.get().hashCode(), Counter.counter.get().toString()); } } private static class Counter { private static ThreadLocal<StringBuilder> counter = new ThreadLocal<StringBuilder>() { @Override protected StringBuilder initialValue() { return new StringBuilder(); }}; }}Copy the code

The example analysis

ThreadLocal itself supports the paradigm. This example uses a ThreadLocal variable of type StringBuilder. A StringBuidler instance can be read using ThreadLocal’s get() method, or a StringBuilder can be set using the set(T T) method.

The result of the above code execution is as follows

Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:0
Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:0
Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:0
Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:01
Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:01
Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:012
Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:0123
Set, Thread name:thread - 3 , ThreadLocal hashcode:372282300,  Instance hashcode:1362597339, Value:hello world
Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:01
Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:012
Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:012
Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:0123
Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:0123
Set, Thread name:thread - 1 , ThreadLocal hashcode:372282300,  Instance hashcode:482932940, Value:hello world
Set, Thread name:thread - 2 , ThreadLocal hashcode:372282300,  Instance hashcode:1691922941, Value:hello worldCopy the code

As you can see from the output above

  • As you can see from lines 1-3, each thread gets a different Instance of StringBuilder through ThreadLocal’s get() method
  • Lines 1-3 show that each thread accesses the same ThreadLocal variable
  • As you can see from the output on lines 7, 12, and 13, as well as line 30, the StringBuilder instance is obtained by getting () on the static Counter field of the Counter class and appending the string, Instead of putting strings appended by all threads into the same StringBuilder, each thread appends the string to its own StringBuidler instance
  • Comparing the output on line 1 with line 15 and combining it with line 38 shows that after using the set(T T) method, the StringBuilder instance pointed to by the ThreadLocal variable is replaced

ThreadLocal principle

ThreadLocal maintains the mapping between threads and instances

Since each thread accessing a ThreadLocal variable has its own copy of a “local” instance. One possible scenario is for ThreadLocal to maintain a Map with keys as threads and values as instances of it within that Thread. When a thread obtains an instance from this ThreadLocal’s get() scheme, it simply takes the thread as the key and finds the corresponding instance from the Map. The scheme is shown in the figure below






This solution satisfies the requirement of one independent backup per thread mentioned above. Each time a new thread accesses the ThreadLocal, a mapping needs to be added to the Map, and at the end of each thread, the mapping should be cleared. There are two questions:

  • Map writing is required for both adding and reducing threads. Therefore, ensure the security of the Map thread. Although there are several ways to implement thread-safe maps, they all more or less require locks to keep threads safe
  • When a thread terminates, it needs to ensure that the corresponding mappings in all ThreadLocal it accesses are removed, otherwise memory leaks may occur. (Methods to avoid memory leaks are described below.)

The issue of locking is one of the reasons the JDK didn’t adopt this solution.

Thread maintains the mapping between ThreadLocal and instance

In the above scenario, the lock problem occurs because multiple threads are accessing the same Map. If the Map is maintained by threads so that each Thread accesses only its own Map, there is no problem with multithreaded writes, and locks are not required. The scheme is shown in the figure below.






In this scheme, there is no lock problem, but since each thread will maintain the mapping between the ThreadLocal variable and the specific instance in its own Map after accessing a certain ThreadLocal variable, if these references (mappings) are not deleted, these ThreadLocal cannot be reclaimed. Memory leaks may occur. How the JDK solves this problem is described below.

Implementation of ThreadLocal in JDK 8

ThreadLocalMap and memory leak

In this scenario, the Map is provided by the static inner class ThreadLocalMap of the ThreadLocal class. Instances of this class maintain a mapping between a ThreadLocal and a specific instance. Unlike a HashMap, each Entry of ThreadLocalMap is a weak reference to a key, as you can see from super(k). In addition, each Entry contains a strong reference to a value.

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

The reason for using weak references is that when there is no strong reference to a ThreadLocal variable, it can be recycled, thus avoiding the memory leak problem described above when ThreadLocal cannot be recycled.

However, there is another kind of memory leak that can occur here. ThreadLocalMap maintains the mapping between ThreadLocal variables and specific instances. When a ThreadLocal variable is reclaimed, the mapping key becomes null and the Entry cannot be removed. Thus, the instance is referenced by the Entry and cannot be reclaimed, causing a memory leak.

Note: Entry is a weak reference, but it is a weak reference to the ThreadLocal type (that is, it is a weak reference to a key), not a weak reference to an instance, so there is no way to avoid instance-specific memory leaks.

Read the instance

The method for reading the instance is shown below

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(); }Copy the code

When reading an instance, the thread first gets its own ThreadLocalMap using the getMap(t) method. As can be seen from the following definition of the method, the instance of ThreadLocalMap is a field of the Thread class, that is, Thread maintains the mapping between the ThreadLocal object and the specific instance, which is consistent with the analysis above.

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

After the ThreadLocalMap is obtained, the map.getentry (this) method is used to obtain the corresponding Entry of this ThreadLocal in the ThreadLocalMap of the current thread. This in this method is the ThreadLocal object currently accessed.

If the obtained Entry is not NULL, the value fetched from the Entry is the corresponding instance of the thread to be accessed. If the Entry is null, the setInitialValue() method is used to set the initial value of the specific instance corresponding to the ThreadLocal variable in the thread.

Setting the initial value

Set the initial value as follows

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; }Copy the code

This method is private and cannot be overridden.

First, get the initialValue through the initialValue() method. This method is public and returns NULL by default. So this method is often overridden in typical usage. In the above example, it is overloaded in an internal anonymous class.

If the object is not null, the mapping between the ThreadLocal object and the initial value of the corresponding instance is directly added to the ThreadLocalMap of the thread. If null, the ThreadLocalMap object is created before the mapping is added to it.

There is no need to worry about thread safety for ThreadLocalMap. Because each thread has one and only one ThreadLocalMap object, and only the thread itself can access it, the ThreadLocalMap is not accessed by other threads, that is, the object is not shared among multiple threads, and there is no thread-safety issue.

Set up the instance

In addition to setting the initialValue of an instance using the initialValue() method, you can also set the value of an in-thread instance using the set method, as shown 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

This method gets the ThreadLocalMap object for that thread, and then directly adds the mapping between the ThreadLocal object (this in the code) and the target instance to the ThreadLocalMap. Of course, if the mapping already exists, just overwrite it. In addition, if the ThreadLocalMap obtained is null, the ThreadLocalMap object is created first.

Preventing memory leaks

For a ThreadLocal object that is no longer in use and has been reclaimed, its corresponding instance in each thread cannot be reclaimed because it is strongly referenced by the Entry of the thread’s ThreadLocalMap, potentially causing a memory leak.

To address this problem, the set method of ThreadLocalMap sets the values of all entries with null keys to NULL through the replaceStaleEntry method, making the values recoverable. In addition, an Entry with a null key and value is set to NULL using the expungeStaleEntry method in the rehash method to make it retrievable. In this way, ThreadLocal prevents memory leaks.

private void set(ThreadLocal<? > key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e ! = null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

Applicable scenario

As mentioned above, ThreadLocal applies to the following two scenarios

  • Each thread needs to have its own separate instance
  • Instances need to be shared among multiple methods, but do not want to be shared by multiple threads

For the first point, each thread has its own instance, which can be implemented in many ways. For example, you can build a separate instance inside a thread. ThreadLoca can fulfill this requirement in a very convenient form.

The second point can be implemented by passing references between methods as long as the first point is satisfied (each thread has its own instance). ThreadLocal makes code less coupled and implementation more elegant.

case

For Java Web applications, sessions hold a lot of information. Most of the time, you need to obtain information from the Session, and sometimes you need to modify the Session information. On the one hand, you need to ensure that each thread has its own separate Session instance. On the other hand, since sessions are required in many places, there is a need for multiple ways to share sessions. If you don’t use ThreadLocal, you can build a Session instance within each thread and pass it across multiple methods, as shown below.

public class SessionHandler { @Data public static class Session { private String id; private String user; private String status; } public Session createSession() { return new Session(); } public String getUser(Session session) { return session.getUser(); } public String getStatus(Session session) { return session.getStatus(); } public void setStatus(Session session, String status) { session.setStatus(status); } public static void main(String[] args) { new Thread(() -> { SessionHandler handler = new SessionHandler(); Session session = handler.createSession(); handler.getStatus(session); handler.getUser(session); handler.setStatus(session, "close"); handler.getStatus(session); }).start(); }}Copy the code

This approach can fulfill the requirements. However, every time a Session is used, the Session object needs to be passed explicitly, and there is a high degree of coupling between methods.

This functionality is re-implemented using ThreadLocal as shown below.

public class SessionHandler { public static ThreadLocal<Session> session = new ThreadLocal<Session>(); @Data public static class Session { private String id; private String user; private String status; } public void createSession() { session.set(new Session()); } public String getUser() { return session.get().getUser(); } public String getStatus() { return session.get().getStatus(); } public void setStatus(String status) { session.get().setStatus(status); } public static void main(String[] args) { new Thread(() -> { SessionHandler handler = new SessionHandler(); handler.getStatus(); handler.getUser(); handler.setStatus("close"); handler.getStatus(); }).start(); }}Copy the code

The modified code with ThreadLocal eliminates the need to pass Session objects between methods and makes it much easier to ensure that each thread has its own independent instance.

If you look at just one point, there are many alternatives. For example, local variables can be created within threads to enable each thread to have its own instance, and static variables can be used to enable variables to be shared between methods. However, ThreadLocal is perfect if you want to keep variables separate between threads and share them between methods.

conclusion

  • ThreadLocal does not solve the problem of sharing data between threads
  • ThreadLocal avoids instance thread-safety problems by implicitly creating separate instance copies in different threads
  • Each thread holds a Map and maintains the mapping of ThreadLocal objects to specific instances. Since the Map is only accessed by the thread holding it, there are no thread-safety or locking issues
  • The Entry of a ThreadLocalMap refers to a ThreadLocal as a weak reference, avoiding the problem that ThreadLocal objects cannot be reclaimed
  • ThreadLocalMap’s set method prevents memory leaks by calling the replaceStaleEntry method to reclaim the value of an Entry object with a null key (that is, a concrete instance) as well as the Entry object itself
  • ThreadLocal is suitable for scenarios where variables are isolated between threads and shared between methods

Java Advanced series