Welcome to King of Concurrency. This is the 23rd article in the series, and the 10th in Platinum.

Speaking of ThreadLocal, you’re familiar with its name. In concurrent programming, it has a high attendance rate and is one of the most frequently asked interview questions, so its importance is self-evident. Sure, it may have left you tossing and turning at night or evasive in an interview. Because while ThreadLocal is simple to use, it doesn’t seem easy to understand how it works.

However, the so-called knowing that there are tigers in the mountains, to the tiger mountain line. In this article, I’ll walk you through how ThreadLocal works and how ThreadLocal works.

There is also a great deal of information available online on how ThreadLocal can be used and how ThreadLocal works. Unfortunately, most of these materials are not fully explained. Some of them skimped on the necessary details, while others were partial, covering only one point.

So, as the Concurrency King Series writes this article, I’ve been thinking about how to introduce ThreadLocal in a concise way that allows readers to understand it thoroughly in a single article, but at the same time avoids getting stuck in a 10,000-word article. To this end, I’ve crafted some illustrations based on the available data to make the article as graphic as possible to help you understand the essentials of ThreadLocal with relative ease. However, each reader’s background is different, so is the understanding. Therefore, for what you think is not clear, I hope you can leave a comment in the comment area. I will try my best to adjust and improve it, so that you can “understand this article”.

First experience of ThreadLocal usage scenario

Teacher’s question: Under what circumstances do YOU need to use a ThreadLocal?

In The Valley of Kings, each hero has his own territory and manor. Within the manor, it is divided into different areas according to the functional responsibilities, such as the area where the wild monsters are kept, and the area where the gold and weapons are stored. Of course, these areas are private to the hero and should not be confused.

Therefore, when Kai fights wild and gets gold, he can put the wild beasts he fights into his own manor, which is his private space. The same is true of The Lanling King and other heroes. The design is shown below:

Now, let’s write some code to simulate the above scenario:

  • Armor is placed in his private space when he fights and earns gold.
  • When the King of Lanling plays wild and gets gold coins, he puts them in his private space.
  • Their Spaces are located in the Valley of Kings.

Here is a piece of code we wrote. In the code, we define a wildMonsterLocal variable to store the wild monsters our heroes get while playing wild. CoinLocal is used to store the gold that the heroes earn. So Kai put the brown bear he had shot into the enclosure and put the 500 gold coins he had got into the gold deposit area. The king of Lanling put the Wolf he had shot into the enclosure and put the 100 gold coins he had won into the gold deposit area.

After a while they took their stash of monsters and their gold coins.

The main example is shown below. As you read the sample code below, focus on the calls to the GET and set methods of a ThreadLocal.

// Private wild circle breeding area
private static final ThreadLocal < WildMonster > wildMonsterLocal = new ThreadLocal < > ();
// Private gold coin storage area
private static final ThreadLocal < Coin > coinLocal = new ThreadLocal < > ();

public static void main(String[] args) {Thread armor = newThread("Armor", () - > {try {
      say("I shot a brown bear today. Let's put it into the enclosure and enjoy it another day!");
      wildMonsterLocal.set(new Bear("Brown bear"));
      say("I killed some soldiers on the way, and I got some gold coins, but I can save them.");
      coinLocal.set(new Coin(500));

      Thread.sleep(2000);
      note("\n After a while... \n");
      say("Got one from the enclosure:", wildMonsterLocal.get().getName());
      say("Current amount in gold deposit area:", coinLocal.get().getAmount());
    } catch(InterruptedException e) {} }); Thread = newThread("The King of Lanling", () - > {try {
      Thread.sleep(1000);
      say("Today hit a wild Wolf, first put it into the enclosure, another day to enjoy!");
      wildMonsterLocal.set(new Wolf("Wolf"));
      say("I killed some soldiers on the way, and I got some gold coins, but I can save them.");
      coinLocal.set(new Coin(100));

      Thread.sleep(2000);
      say("Got one from the enclosure:", wildMonsterLocal.get().getName());
      say("Current amount in gold deposit area:", coinLocal.get().getAmount());
    } catch(InterruptedException e) {} }); Armoured. Start (); Lanling king. The start (); }Copy the code

The class used in the sample code looks like this:

@Data
private static class WildMonster {
  protected String name;
}

private static class Wolf extends WildMonster {
  public Wolf(String name) {
    this.name = name; }}private static class Bear extends WildMonster {
  public Bear(String name) {
    this.name = name; }}@Data
private static class Coin {
  private int amount;

  public Coin(int amount) {
    this.amount = amount; }}Copy the code

The example code runs as follows:

Kai: today hit a brown bear, first put it into the enclosure, another day to enjoy! Kai: I killed some soldiers on the road and got some gold coins, but save them first! Lanling king: I hit a Wolf today. Let's put it into the enclosure and enjoy it another day! Lanling King: I killed some soldiers on the road and got some gold coins. Save them first! After a while... Armor: Got one from the captive area: Brown bear armor: Gold coin storage area current amount: 500 Lanling King: Got one from the captive area: Wild Wolf Lanling King: Gold coin storage area current amount: 100 Process finished with exit code 0Copy the code

From the results of the operation, it can be clearly seen that after a while, Kai and The King of Lanling respectively got the wild monsters and gold coins they had stored before, and they were exactly the same.

This is typical of ThreadLocal applications. In a concurrent multi-threaded scenario, if you need to set private variables for each thread that span the class and method levels, then you need to consider using ThreadLocal. Note that the two main points here are that variables are unique to one thread, and that variables can be shared between different methods or even different classes.

ThreadLocal can be used in many scenarios in software design. As a simple example, if you need to set a traceId to trace the entire call link of a request, you need a variable that can span classes and methods. This variable can be fetched by threads in different classes without error, as shown in the figure below:

Second, ThreadLocal principle resolution

With ThreadLocal, one of the most commonly asked questions is probably the classic interview question: Talk about your understanding of ThreadLocal memory leaks. This may seem like a simple question, but to answer it to the point, you must have a good understanding of the source code. Of course, it’s okay to recite answers to interview questions that rant about “soft references” and “memory recycling.” After all, most interviewers are half-qualified.

Next, we’ll use the above scenario and its sample code to explain how ThreadLocal works, allowing you to find the real answer to this question.

1. Source code analysis

If you’re having trouble understanding ThreadLocal, chances are you’re not making sense of the different concepts. So, the first step in understanding the ThreadLocal source code is to identify its related concepts and clarify the relationship between them, namely Thread, ThreadLocalMap, and ThreadLocal. It’s these three key concepts that make a great show. Of course, if you want to subdivide, you can also separate the Entry.

Key concept 1: Thread class

Why Thread ranks first among the key concepts is that ThreadLocal is designed for it. What is the relationship between Thread and ThreadLocal? Let’s look at the source code for Thread:

class Thread implements Runnable {.../* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null; . }Copy the code

Nothing is clearer than source code presentation. As you can see intuitively, Thread has a variable: threadLocals. This variable is used to store the private data of the current thread, and can be used to store multiple private data. After all, threads can carry multiple private data, such as traceId, userId and so on. After understanding the purpose of this variable, look at its types, namely ThreadLocal. ThreadLocalMap. You see, Thread is thus related to ThreadLocal, so let’s look at another key concept.

Key concept 2: the ThreadLocalMap class

As you can see from Thread’s source code, Thread uses ThreadLocalMap to store thread-private data. Let’s take a look at the source code of ThreadLocalMap, leaving out ThreadLocal for a moment:

static class ThreadLocalMap {.../** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. * /
        static class Entry extends WeakReference<ThreadLocal<? >>{
            /** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}/** * The table, resized as necessary. * table.length MUST always be a power of two. */
        privateEntry[] table; . }Copy the code

The most critical attribute in ThreadLocalMap is the Entry[] table, which enables thread-private storage of multiple data. Entry inherits WeakReference, and the Key type of Entry is ThreadLocal. At this point, forget about the rest of ThreadLocalMap’s source code. You should now understand that tables are where thread-private data is stored, and that the rest of ThreadLocalMap’s source code exists only for storing and fetching table data. This is the key to your understanding of ThreadLocalMap; don’t get lost in the intricacies of other source code.

Key concept 3: ThreadLocal classes

Now, finally, you’re looking at the ThreadLocal class. Thread uses ThreadLocalMap, and you’ll see later that ThreadLocal merely encapsulates some of the operations on ThreadLocalMap. See, the ThreadLocal methods get(), set(), remove(), and so on all operate on the ThreadLocalMap. Before any operation, the ThreadLocalMap of the current thread is obtained through the getMap() method.

public class ThreadLocal<T> {...// Retrieve data for the current thread
    public T get(a) {
        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();
    }
    // Initialize the data
    private T setInitialValue(a) {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    // Set data for the current 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);
    }

     public void remove(a) {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if(m ! =null)
             m.remove(this);
     }

    // Get the key to the thread-private data store, which operates in the ThreadLocal, but operates in the threadLocals variable of the Thread
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    // Initialize the thread's t.hreadlocals variable and set it to null
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue); }... }Copy the code

If, at this point, you’re still confused about the concepts and their source code, you’re right. The following graph, which combines the concepts and sample code to restore the relevant concepts and their relationships, is worth repeating.

In the picture above, you need the following details:

  • There are two threads: armored and Lanling King;
  • There are two ThreadLocal objects, one for the thread’s private data, the heroes’ monsters and the gold.
  • Thread armouredandThread Lanling kingHave a ThreadLocal. ThreadLocalMap variable, used to store different ThreadLocal, namelywildMonsterLocalandcoinLocalBoth of these variables go into ThreadLocalMaptableInside the entry array;
  • When a thread puts data into a ThreadLocalMap, its key points to the ThreadLocal object, and its value is the value in the ThreadLocal. For example, whenarmouredwillBrown bearIn thewildMonsterLocal, the key of the corresponding Entry iswildMonsterLocal, and value isnew Bear(), i.e.,Brown bear.When the King of Lanling put in the wild monster, the same thing; The same goes for gold coins;
  • When the Entry key points to a ThreadLocal object, such aswildMonsterLocalorcoinLocalWhen, notice,Is the virtual reference.Is the virtual reference.Is the virtual reference.Is the virtual reference! Important things,Say four times. See the red dotted line in the diagram, or in the ThreadLocalMap source codeWeakReference.

If you’ve seen the above picture, then you should have seen the relationship in the following picture. Otherwise, if you don’t seem to understand it, go back to the drawing above and continue until you can see the picture below.

2. Usage Guide

Next, I’ll give you a rundown of some common, high-frequency uses of ThreadLocal.

(1) Create a ThreadLocal

Just create it like any other object, nothing special.

ThreadLocal < WildMonster > wildMonsterLocal = new ThreadLocal < > ();
Copy the code

After the object is created, each thread can read and write data to it. Of course, each thread can only see its own data.

(2) Set the value of ThreadLocal

wildMonsterLocal.set(new Bear("Brown bear"));
Copy the code

(3) Take out the value of ThreadLocal

wildMonsterLocal.get();
Copy the code

One thing to note when reading the data is that if no data has been set in at this point, the setInitialValue method will be called to set the initial value and return it to the caller.

(4) Initialize the value of ThreadLocal

private ThreadLocal wildMonsterLocal = new ThreadLocal<WildMonster>() {
    @Override 
    protected WildMonster initialValue(a) {
        return newWildMonster(); }};Copy the code

If you want to set an initialValue, you can override its initialValue method if you want to set an initialValue.

3. How do I understand memory leaks in ThreadLocal

First, you need to understand the concept of weak references. In Java, references are classified into different reference types, such as strong reference, weak reference, soft reference, and unreal reference. Different reference types correspond to different garbage collection policies. If you are not familiar with this, I suggest you go to search for relevant information, you can also see this article.

For weak references, when the garbage collector thread scans the area of memory it manages, once an object with only weak references is found, its memory is reclaimed regardless of whether the current memory space is sufficient. However, because the garbage collector is a low-priority thread, objects with only weak references are not necessarily quickly discovered. But even occasional occurrences are enough to cause problems.

Now that you understand weak references and the corresponding garbage collection strategy, go back to the above diagram:

In this diagram, the Entry key refers to the ThreadLocal object using a weak reference, which is indicated by a red arrow. Does this red dotted line here cause the problem up here? If you think about it, if the ThreadLocal object is reclaimed, then the key in the Entry is programmed null. However, although the key (wildMonsterLocal) becomes null, the value of the value (new Bear(” brown Bear “)) is still a strong reference, it will continue to exist, but it is actually useless, so this Entry will become invalid, but the existence of the value cannot be recycled. Thus, the memory leak is created.

So why use weak references?

I’m sure you have this question, if not, you may want to read this article again. The reason for using weak references knowing that there is a risk of a memory leak here is that when ThreadLocal objects do not have strong references, they need to be cleaned up, otherwise they persist in the ThreadLocalMap, which is also a memory leak. You see, the problem is that one link leads to another.

Best practice: How to avoid memory leaks

So, now that this is the case, how do you avoid memory leaks? A possible best practice is to manually execute the remove() method after the call completes.

private static final ThreadLocal<WildMonster> wildMonsterLocal = new ThreadLocal<>();

try{ wildMonsterLocal.get(); . }finally{
    wildMonsterLocal.remove();
}

Copy the code

In addition, ThreadLocal also provides a scheme: when the set method is called, the replaceStaleEntry method is called to check for entries with a null key. If an Entry is found with a null key, its value is also set to NULL, so that the Entry can be reclaimed. Of course, if you don’t call the set method again, then the scheme is invalid.

private void set(ThreadLocal < ? > key, Object value) {...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); / / look here
             return; }}... }Copy the code

summary

That’s all about ThreadLocal. When learning about ThreadLocal, the first thing to understand is its application scenario, the problem it is intended to solve. Secondly, we should have a certain understanding of its source code. When you look at the source code, it’s important to start with the concepts of Thread, ThreadLocal, and ThreadLocalMap, and understand the relationship between them. In this way, you can fully understand how common memory leaks are.

At the end of the text, congratulations on your another star ✨

The teacher’s trial

  • Try to explain to your friends how ThreadLocal memory leaks happen.

Further reading and references

  • “King concurrent course” outline and update progress overview

About the author

Pay attention to [technology 8:30], get the article updates in time. Pass on quality technical articles, record the coming-of-age stories of ordinary people, and occasionally talk about life and ideals. 8:30 in the morning push author quality original, 20:30 in the evening push industry depth good article.

If this article is helpful to you, welcome to like, follow, supervise, we together from bronze to king.