Functional iteration. There are a million ways to implement it at the code level. Let’s take a look at how ThreadLocal gracefully completes this iteration.

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

Understanding erroneous zone

Prior to writing this article, I’ve read a number of blogs on the web about how ThreadLocal works and the problems it solves, and they’re not very clear, or even misleading. For example, the following is a common introduction to ThreadLocal (wrong)

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

After a lot of reading and hands-on experiments, ThreadLocal is not designed to solve the problem of multithreading sharing variables as described above.

A correct understanding

ThreadLoal, the basic principle of which is that all objects contained in the same ThreadLocal (in the case of ThreadLocal< StringBuilder >), There are different copies (actually different instances) of different threads:

  • Each Thread has its own instance copy that can only be used by the current Thread.
  • Since other threads are not accessible, there is no problem with multithreading sharing.

Here’s how the official document describes it:

After READING it, I came to this conclusion

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 ThreadLocal instances it uses are recycled.

ThreadLocal is therefore ideal for scenarios where each thread needs its own independent instance and that instance needs to be used in multiple methods. Of course, the same effect could have been achieved in other ways, but after reading this article, you’ll see that ThreadLocal makes the implementation much cleaner and more elegant!

ThreadLocal usage

The sample code

Let’s use the following code to make an example, and then analyze the phenomenon and come to a conclusion:

public class ThreadLocalDemo {

    public static void main(String[] args) throws InterruptedException {
        int threadNum = 3;
        CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        for (int i = 1; i <= threadNum; i++) {
            new Thread(() -> {
                for (int j = 0; j <= 2; j++) {
                    MyUtil.add(String.valueOf(j));
                    MyUtil.print();
                }
                MyUtil.set("hello world");
                countDownLatch.countDown();
            }, "thread - " + i).start();
        }
        countDownLatch.await();
    }

    private static class MyUtil {

        public static void add(String newStr) {
            StringBuilder str = StringBuilderUtil.stringBuilderThreadLocal.get();
            StringBuilderUtil.stringBuilderThreadLocal.set(str.append(newStr));
        }

        public static void print() {
            System.out.printf("Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
                    Thread.currentThread().getName(),
                    StringBuilderUtil.stringBuilderThreadLocal.hashCode(),
                    StringBuilderUtil.stringBuilderThreadLocal.get().hashCode(),
                    StringBuilderUtil.stringBuilderThreadLocal.get().toString());
        }

        public static void set(String words) {
            StringBuilderUtil.stringBuilderThreadLocal.set(new StringBuilder(words));
            System.out.printf("Set, Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n", Thread.currentThread().getName(), StringBuilderUtil.stringBuilderThreadLocal.hashCode(), StringBuilderUtil.stringBuilderThreadLocal.get().hashCode(), StringBuilderUtil.stringBuilderThreadLocal.get().toString()); }} private static class StringBuilderUtil {// ThreadLocal is usually modified by private static ThreadLocal<StringBuilder> stringBuilderThreadLocal = ThreadLocal.withInitial(() -> new StringBuilder()); }}Copy the code

The example analysis

ThreadLocal itself supports generics, such as the StringBuilder ThreadLocal variable used in this example. A StringBuidler instance can be read using ThreadLocal’s get() method, or a StringBuilder can be set using the set(T T) method.

Tips: The CountDownLatch class, located under the java.util.Concurrent package, enables counter like functionality. For example, in A scenario where task A waits for four other tasks to complete, CountDownLatch can be used for this purpose. Next time, we can talk about this feature alone.

Click Run and the console outputs the result

We can find:

  • Each thread accesses the same ThreadLocal variable and gets a different StringBuilder instance through ThreadLocal’s get() method.
  • Although the StringBuilderUtil class static stringBuilderThreadLocal field gets () to get the StringBuilder instance and appends the string, Instead of putting strings appended by all threads into the same StringBuilder, each thread appends the string to its own StringBuidler instance
  • After using the set(T T) method, the StringBuilder instance pointed to by the ThreadLocal variable is replaced

ThreadLocal principle

Plan a

Let’s go out on a wild guess, since every thread that accesses a ThreadLocal variable has its own copy of a “local” instance. One possible scenario is for ThreadLocal to maintain a Map where the Key is the current thread and the Value is an instance of ThreadLocal within the current thread. Thus, when a thread obtains an instance from the ThreadLocal get() scheme, it simply needs to find the corresponding instance from the Map using the thread as the key. The scheme is shown in the figure below

This solution satisfies the requirement of having a backup ThreadLocal instance within each thread mentioned above. Each time a new thread accesses the ThreadLocal, a new mapping is added to the Map, and the mapping is cleared when each thread terminates. But, there are two problems:

  • The Map needs to be updated both when a thread is started and when a thread is terminated, so the Map must be thread 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.

Thread safety was a major reason the JDK didn’t adopt this approach.

Scheme 2

In this scenario, there are synchronization problems that can occur when multiple threads access the same Map. This problem does not exist if the Map is maintained by threads, so that each Thread accesses only its own Map. The scheme is shown in the figure below.

This scenario does not have locking problems, but since each thread maintains a mapping of a ThreadLocal variable to a specific instance in its own Map after accessing it, it can cause memory leaks if these references (mappings) are not removed. Let’s take a look at how Jdk8 solves this problem.

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 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

Weak references are used because when there are no strong references to a ThreadLocal variable, the variable can be reclaimed, avoiding the memory leak that ThreadLocal would cause if it could not be reclaimed.

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 to a ThreadLocal type, not a weak reference to a specific instance, so there are instance-specific memory leaks.

Read the instance

Let’s look at how ThreadLocal gets an instance

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

When a thread gets an instance, it 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.

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

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. But every time you need to use a Session, you need to explicitly pass the Session object, and the coupling between methods is high and not elegant.

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

public class SessionHandler {

  public static ThreadLocal<Session> session = ThreadLocal.<Session>withInitial(() -> new Session());

  @Data
  public static class Session {
    private String id;
    private String user;
    private String status;
  }

  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

As you can see, the modified code eliminates the need to pass Session objects back and forth between methods and effortlessly ensures that each thread has its own independent instance. Looking at one point, though, there are plenty of alternatives. For example, local variables can be created within threads to ensure that each thread has its own instance, and variables can be shared between methods through static variables. But if you also need to keep variables isolated between threads and shared between methods, ThreadLocal is a good fit.

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