preface

We are all familiar with ThreadLocal in Java, and it is often found in various articles and practical applications. This article introduces the basic use and implementation of threadLocal. Why are ThreadLocal keys weak references? Why are ThreadLocal keys leaking

What is it? What does it do

In a multi-threaded environment, if each thread wants to maintain variables that belong to its own thread, it usually has to encapsulate them in methods, because variables in the method stack cannot be accessed by other threads

ThreadLocal provides the ability to provide a variable that each thread operates on locally, without affecting its value in other threads

This variable can no longer be a local variable defined in a method, but a property of an instance, or a static property of a class (recommended), which makes it easy to manipulate thread-only variables between different methods

By the way, go doesn’t have anything like ThreadLocal, which passes data directly through the Context on a request, so each method takes an extra parameter. This, of course, has become the GO code specification

The basic use

From the user’s perspective, ThreadLocal is essentially a variable, so it should have set, GET, remove, and initinal methods

The ThreadLocal API is as follows:

There are two initialization methods, and one initialization function is set at the bottom. The last static method withInitinal is recommended because it is more concise

The last program demonstrates its basic usage

public class ThreadLocalTest {



    // Define the ThreadLocal variable with an initial value of 1

    static ThreadLocal<Integer> tl = ThreadLocal.withInitial(()->1);



    public static void main(String[] args) throws InterruptedException {



        // Start two threads to increment the TL separately

        new Thread(()->{

            Integer v1 = tl.get();

            v1++;

            tl.set(v1);

            System.out.println(Thread.currentThread().getName() + "-" + tl.get());

        }, "t1" ).start();



        new Thread(()->{

            Integer v1 = tl.get();

            v1++;

            tl.set(v1);

            System.out.println(Thread.currentThread().getName() + "-" + tl.get());

        }, "t2" ).start();



        Thread.sleep(1000);

        System.out.println(Thread.currentThread().getName() + "-"+ tl.get()); }}Copy the code

The program uses ThreadLocal on t1, T2, and main threads, respectively, where t1 and T2 are aligned to increment.

t1---2

t2---2

main---1
Copy the code

You can see that each thread’s operations on the ThreadlLocal variable affect only its in-thread replicas. Matches the definition of ThreadlLocal

Since it can only be accessed by each thread, there is no problem of multithreading sharing, so v1++, a non-atomic operation, does not need to be locked to synchronize

Source code analysis

To find out how ThreadLocal works, read the source code

Firstly, analyze the structure of the class:

What is the relationship between threads and ThreadLocal?

Thread in the Thread class has a property: ThreadLocal. ThreadLocalMap

ThreadLocalMap is a static inner class of ThreadLocal

Let’s look at the get and set methods of ThreadLocal

Get source code as follows:

public T get(a) {

    Thread t = Thread.currentThread();

    ThreadLocalMap map = getMap(t);

    if(map ! =null) {

        ThreadLocalMap.Entry e = map.getEntry(this);

        if(e ! =null) {

            T result = (T)e.value;

            returnresult; }}return setInitialValue();

}
Copy the code

Get returns the value of the current thread-local variable

  1. Gets the ThreadLocalMap of the current thread, directly from the threadLocals variable of the current thread
  2. If the map is not empty, get the value from the map with key this, which is the ThreadLocal itself
  3. If the map is empty, initialize the map

If set has not been used before, the map must be empty and the setInitialValue method will be used.

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;

}
Copy the code

Steps as follows:

  1. Call the initialValue method to generate the initialValue
  2. Call the createMap method to create a ThreadLocalMap and place the initial values into the map

Set method is similar, the source code is as follows, do not repeat

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

When the map is empty at first, it goes to the createMap method:

void createMap(Thread t, T firstValue) {

    t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table =new Entry[INITIAL_CAPACITY];

    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

    / / the key

    table[i] = new Entry(firstKey, firstValue);

    size = 1;

    setThreshold(INITIAL_CAPACITY);

}
Copy the code

You can see that the map consists of entries, each of which consists of ThreadLocal itself and a value

The memory structure of ThreadLocal is now clear:

All data is stored locally on the thread

Why does Entry inherit WeakReference

Entry is also a static inner class of ThreadLocal:

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

It inherits WeakReference and calls super(k) in the constructor, so that a WeakReference points to the ThrealLocal variable passed in

Then the question comes, what is WeakReference and why should Entry inherit it?

Let’s review javA4 types of references:

  • Strong references: This is the most common object reference method. When an object is strongly referenced, it will not be collected by the garbage collection mechanism, even if OOM appears
  • SoftReference: a SoftReference is a reference that is weaker than a strong reference. If an object is referenced only by a SoftReference, the object will not be reclaimed if the system memory is sufficient. Otherwise, the object will be reclaimed. Usually used in cache scenarios, avoid OOM due to large cache
  • WeakReference: unlike if references, if an object is only referenced by a soft reference, it will be reclaimed as soon as GC occurs
  • PhantomReference: this type of reference does not determine the life cycle of the object. It is used together with reference queues to receive system notification when the object is reclaimed. It is generally not used in daily development

Consider the following scenario:

public void func1(){

    ThreadLocal<Object> tl = ThreadLocal.withInitial(()->1);

    Object o = new Object();

    tl.set(o);

    tl.get();

}
Copy the code

If the Entry to key is a strong reference, when func1 is executed, the stack frame will be recycled and no strong application will point to the TL. However, if the thread is not terminated, then the key of an Entry of the thread’s ThreadLocalMap will still point to the strong reference of the TL. Although the TL is no longer needed, the object will not be collected by gc due to strong references, causing a memory leak

The strong reference chain is Thread -> ThreadLocalMap -> Entry -> key, as follows:

Therefore, Java officially makes Entry inherit WeakReference, so that the key of the Entry is pointed to by WeakReference. Func1’s key has only a weak reference to it, so it will be recycled whenever garbage collection is run, resulting in null entries in ThreadLocalMap

After that, the set, GET, and remove calls of ThreadLocal will attempt to delete entries with null keys to release the memory occupied by the value, avoiding memory leaks

Of course, the best practice is to define ThreadLocal as a static or class property and then call remove to clean up the data after use, so that you can avoid memory leaks without relying on weak references

Thread pool scenario

In the thread pool scenario, since threads are not destroyed after processing a single piece of business logic but are put back into the thread pool, previously stored ThreadLocal variables are also retained

ThreadLocal stores data related to one request, such as userID, which may cause problems if the data is retrieved by the next request

Therefore, after using ThreadLocal, you need to call the remove method to remove the data

conclusion

  1. ThreadLocal is suitable for storing data that is isolated between threads but shared between methods
  2. Because the data is stored in thread-local variables and can only be accessed by the current thread itself, there is no thread-safety issue
  3. The Entry of a ThreadLocalMap references the key (ThreadLocal) as a weak reference, avoiding memory leaks
  4. The best practice is to define ThreadLocal as a static or class property and then call remove to clean up the data after use, thus avoiding memory leaks without relying on weak references