I think ThreadLocal is something that you all know a little bit about. When I first got into ThreadLocal, I thought it was amazing. I read a lot of blogs on the Internet, I read some books. Fortunately, this thing is not used much in the actual development, so we muddle along. Of course, I don’t say much, but for general upper-level business development, ThreadLocal is used in many frameworks, and some have even improved on ThreadLocal. But ThreadLocal is also the foundation of concurrent programming, so it’s really worth looking into. Today we’ll take a closer look at ThreadLocal.

Simple application of ThreadLocal

We know that in multi-threading, it is easy to operate a shared variable, contradiction, to solve this problem, the best way is of course, each thread has its own variable, other threads can not access, the so-called “no sharing, no harm”. So how do you do that? That’s where ThreadLocal comes in.

Let’s start with a simple application:

    public static void main(String[] args) {
        ThreadLocal threadLocal = new ThreadLocal();
        threadLocal.set("Hello");
        System.out.println("Current thread is:" + Thread.currentThread().getName());
        System.out.println("Get in current thread:" + threadLocal.get());
        new Thread(() -> System.out.println("Now the thread is"+Thread.currentThread().getName()+"Try to get:" + threadLocal.get())).start();
    }
Copy the code

Running results:

The current Thread is: main gets: Hello in the current Thread The current Thread is thread-0 try to get: nullCopy the code

The result is fairly straightforward: a value is inserted into a threadLocal thread on the main thread, and can only be retrieved on the same thread, but not on other threads.

Try writing your own ThreadLocal

Before we dive into ThreadLocal, let’s think about what you would do if asked to implement a ThreadLocal. The goal of ThreadLocal is for each thread to have its own variables. The most straightforward way to do this is to create a new generic class that defines a map with a key of type Long to hold the thread ID and a value of type T to hold specific data.

  • When set, it gets the id of the current thread, uses this as the key, and inserts data into the map.

  • When you get, you still get the id of the current thread, use that as the key, and then pull the data out of the map.

It looks like this:

public class ThreadLocalTest {
    public static void main(String[] args) {
        CodeBearThreadLocal threadLocal = new CodeBearThreadLocal();
        threadLocal.set("Hello");

        System.out.println("Current thread is:" + Thread.currentThread().getName());
        System.out.println("Get in current thread:" + threadLocal.get());
        new Thread(() -> System.out.println("Now the thread is" + Thread.currentThread().getName() + "Try to get:" + threadLocal.get())).start();
    }
}

class CodeBearThreadLocal<T> {
    private ConcurrentHashMap<Long , T> hashMap = new ConcurrentHashMap<>();

    void set(T value) {
        hashMap.put(Thread.currentThread().getId(),value);
    }

    T get() {
       return hashMap.get(Thread.currentThread().getId()); }}Copy the code

Running results:

The current Thread is: main gets: Hello in the current Thread The current Thread is thread-0 try to get: nullCopy the code

You can see that the result is exactly the same as the “real ThreadLocal”.

To explore the ThreadLocal

We wrote a ThreadLocal of our own that didn’t seem to have any problems, and just a few lines of code made it work. So how does a legitimate ThreadLocal work? I think the core is pretty much what we wrote. Unfortunately, the original ThreadLocal doesn’t look anything like the one we wrote.

Let’s now take a look at what the original ThreadLocal does.

set

    public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); / / get ThreadLocalMapif(map ! = null)// If map is not null, callsetSet (this, value);elsecreateMap(t, value); // create a map}Copy the code
  1. Gets the current thread assignment to t;
  2. Call getMap, pass in t, that is, pass in the current thread, get ThreadLocalMap, assign to map;
  3. If map is not null, the set method is called to insert the value.
  4. If map is null, the createMap method is called.

Let’s look at the getMap method:

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

ThreadLocals is defined by Thread locals (ThreadLocalMap), which is provided by the Thread locals function.

public class Thread implements Runnable{
    ThreadLocal.ThreadLocalMap threadLocals = null;
}
Copy the code

A set of ThreadLocal maps is a set of a ThreadLocal map. A set of ThreadLocal maps is a set of a ThreadLocal map.

Let us the relationship between the principle way, really is a little confusing, Thread class defines the ThreadLocal. ThreadLocalMap fields, ThreadLocalMap is TheadLocal internal static class, including the Entry of [] is used to store data. This means that threadLocalMaps in each Thread instance are unique and do not interfere with each other. Wait, doesn’t that demystify ThreadLocal? ThreadLocal allows each thread to have its own variable.

If you don’t know, don’t worry, let’s go into more details. In our implementation of ThreadLocal, we use a map to store data, the key is the Thread Id, the key is the Thread instance, the value is the data we need to save, when we call get, we use the Thread Id, You can use the Thread instance to fetch data from the map, so that the data we fetch must be owned by the Thread. If this Thread is Thread A, and you pass in the Thread Id of Thread B, then the instance of Thread B will not be able to retrieve the data held by Thread A. However, in a legitimate ThreadLocal, data is stored directly in Thread instances, so that data for each Thread is naturally isolated.

Now we have solved the problem of how ThreadLocal implements thread data isolation, but there is still a problem that I am still puzzled by after reading a lot of blogs about ThreadLocal. Since the data is stored in Entry[] of ThreadLocalMap, This means that you can store multiple data, otherwise you can use a common member variable, why use an array? However, ThreadLocal does not overload the set method. If a “hello” is set and a “bye” is set, the “bye” will override the “hello”. We can create multiple ThreadLocal’s, like this: ThreadLocal = ThreadLocal; ThreadLocal = ThreadLocal; ThreadLocal = ThreadLocal;

    public static void main(String[] args) {
        ThreadLocal threadLocal1 = new ThreadLocal();
        threadLocal1.set("Hello");

        ThreadLocal threadLocal2 = new ThreadLocal();
        threadLocal2.set("Bye");
    }
Copy the code

So what happens? Here’s the set code again, so you don’t have to scroll up too long:

    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

ThreadLocal1 and threadLocal2 both call the set method. Although threadLocal1 and threadLocal2 are different instances, they are on the same thread, so getMap gets the same ThreadLocalMap. This results in multiple data stores in the same ThreadLocalMap.

How to save the data specifically, this code is more complex, including too many details, I do not understand, only a general, let’s first look at the definition of Entry:

    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
}
Copy the code

Entry is a static inner class of ThreadLocalMap that contains only one field, value, that is, unlike HashMap, there is no concept of a linked list.

        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
  1. Assign table to the local variable TAB. This table is the field that holds the data and is of type Entry[];
  2. Get the length of TAB and assign it to len;
  3. Find the subscript I;
  4. If e==null, the loop will not enter the for loop. If e==null, the loop will enter step 5. If the current location already has data, determine if the ThreadLocal is the same as the one we are about to insert, and if so, replace it with the new value. If not, look for the next vacancy;
  5. Place the created Entry instance into TAB.

The details are a little bit too much to read, but the most important thing should be understood.

get

   public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // Pass in the current thread to get the ThreadLocalMap of the current threadif(map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); // Pass in an instance of ThreadLocal to get an Entryif(e ! = null) { @SuppressWarnings("unchecked")
                T result = (T)e.value;
                returnresult; // Return value}}return setInitialValue();
    }
Copy the code
  1. Get the current thread;
  2. Get the ThreadLocalMap of the current thread;
  3. Pass in an instance of ThreadLocal to get Enrty.
  4. The return value.
private Entry getEntry(ThreadLocal<? > key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i];if(e ! = null && e.get() == key)return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
Copy the code
  1. Find the subscript I;
  2. According to the subscript I, take the value from the table and assign it to e;
  3. If e is not null and the ThreadLocal instance held by e is the same as the ThreadLocal instance passed in, return.
  4. If e is empty, or if e holds a different ThreadLocal instance than the ThreadLocal instance passed in, continue searching.

A small summary

Now that the set and GET methods are analyzed, let’s make a quick summary. The ThreadLocal we use outside is more like a utility class, holding no data of its own, while the real data is stored in the Thread instance, thus achieving natural isolation of Thread data. To help you understand ThreadLocal better:

Memory leaks

Let’s look at the definition of Entry:

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

Entry inherits WeakReference. As for what WeakReference is, it is not the focus of this paper, so we can refer to it by ourselves. WeakReference wraps ThreadLocal. Let’s look at the Entry constructor, which calls super(k) and passes in the ThreadLocal instance we passed in, that is, ThreadLocal is saved in WeakReference object. This causes a problem. When a ThreadLocal does not have strong dependencies, the ThreadLocal will be reclaimed on the next GC. The key will be reclaimed but the value will not be reclaimed, so Entry[] will have a NULL key but not a NULL value. To recycle, let the thread that created the ThreadLocal expire. But in real development, threads are most likely to live and die with the program, bouncing around as long as the program doesn’t stop. So it’s best to call the remove method manually after using the ThreadLocal method, like this:

    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal();
        try {
            threadLocal.set("Hello"); threadLocal.get(); } finally { threadLocal.remove(); }}Copy the code

Remember, it’s best to put the remove method in finally.

InheritableThreadLocal

Let’s look at the example from the beginning of this blog:

  public static void main(String[] args) {
        ThreadLocal threadLocal = new ThreadLocal();
        threadLocal.set("Hello");
        System.out.println("Current thread is:" + Thread.currentThread().getName());
        System.out.println("Get in current thread:" + threadLocal.get());
        new Thread(() -> System.out.println("Now the thread is" + Thread.currentThread().getName() + "Try to get:" + threadLocal.get())).start();
    }
Copy the code

Running results:

The current Thread is: main gets: Hello in the current Thread The current Thread is thread-0 try to get: nullCopy the code

The new Thread is created by the main Thread, so we can say that this Thread is a child of the main Thread, and we can’t get the value of the ThreadLocal set from the main Thread from the child Thread, which makes sense because they’re not the same Thread, But I want the child thread to inherit data from the ThreadLocal of the main thread. [InheritableThreadLocal] [InheritableThreadLocal] [InheritableThreadLocal]

    public static void main(String[] args) {
        ThreadLocal threadLocal = new InheritableThreadLocal();
        threadLocal.set("Hello");
        System.out.println("Current thread is:" + Thread.currentThread().getName());
        System.out.println("Get in current thread:" + threadLocal.get());
        new Thread(() -> System.out.println("Now the thread is" + Thread.currentThread().getName() + "Try to get:" + threadLocal.get())).start();
    }
Copy the code

Running results:

The current Thread is: main gets: Hello in the current Thread the current Thread is thread-0 try to get: HelloCopy the code

This allows the child thread to inherit data from the main thread’s ThreadLocal, or, more accurately, from the parent thread’s ThreadLocal.

So how does that work? Let’s look at the code.

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       returnt.inheritableThreadLocals; } void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }}Copy the code

InheritableThreadLocal inherits ThreadLocal and overrides three methods. When we first call our set with InheritableThreadLocal, Call the createMap method with InheritableThreadLocal, which creates an instance of ThreadLocalMap and assigns it to the inheritableThreadLocals, Where is this inheritableThreadLocals definition? As with threadLocals, it is defined in the Thread class. When we call the set method again, we call the inheritableThreadLocals getMap method, which returns the inheritableThreadLocals function.

When we create a Thread, we call the Thread constructor:

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
Copy the code

The init method is longer, so I’ll just copy the code that’s relevant to the problem we’re exploring:

 Thread parent = currentThread();
 if(inheritThreadLocals && parent.inheritableThreadLocals ! = null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);Copy the code
  1. Gets the current thread, which is the parent thread.
  2. If the parent thread’s inheritableThreadLocals isn’t empty, it goes to if. If we call the set method in InheritableThreadLocal, what’s going on in inheritableThreadLocals, if, If you pass in the parent thread’s inheritableThreadLocals, create a new ThreadLocalMap, and then assign it to Thead’s inheritableThreadLocals, the thread will own the parent thread’s ThreadLocalMap. This completes the inheritance and passing of ThreadLocal.

That’s the end of this blog post, but there are a lot of things that are important, especially the causes of ThreadLocal and the causes of memory leaks and ways to avoid them.