java thread knowledge ThreadLocal

Posted by minou on Fri, 28 Jan 2022 06:23:00 +0100

1, Introduction to ThreadLocal

When multiple threads access the same shared variable, it is easy to have concurrency problems, especially when multiple threads write to a variable. In order to ensure thread safety, general users need to take additional synchronization measures when accessing shared variables to ensure thread safety. ThreadLocal is a way to avoid thread insecurity in multi-threaded access in addition to locking. When we create a variable, if each thread accesses its own variables, there will be no thread insecurity.

ThreadLocal is provided by the JDK package. It provides thread local variables. If a ThreadLocal variable is created, each thread accessing this variable will have a copy of this variable. In the actual multi-threaded operation, the variables in its own local memory are operated, so as to avoid thread safety problems, as shown in the following figure

2, How to create ThreadLocal variable

The following code shows how to create a ThreadLocal variable:

1private ThreadLocal myThreadLocal = new ThreadLocal();

We can see that a ThreadLocal object is instantiated through this code. We only need to instantiate the object once, and we don't need to know which thread instantiates it. Although all threads can access this ThreadLocal instance, each thread can only access the value set by calling the set() method of ThreadLocal. Even if two different threads set different values on the same ThreadLocal object, they still cannot access each other's values.

3, How to access ThreadLocal variable

Once a ThreadLocal variable is created, you can set a value to be saved through the following code:

1myThreadLocal.set("A thread local value");

You can read the value saved in ThreadLocal variable by the following method:

1String threadLocalValue = (String) myThreadLocal.get();

The get() method returns an Object object, and the set() Object needs to pass in a parameter of Object type.

4, ThreadLocal is easy to use

In the following example, we open two threads, set the values of local variables inside each thread, and then call the print method to print the value of the current local variables. If you call the local variable's remove method after printing, the variables in the local memory will be deleted. The code is shown below.

package test;

public class ThreadLocalTest {

    static ThreadLocal<String> localVar = new ThreadLocal<>();

    static void print(String str) {
        //Prints the value of a local variable in local memory in the current thread
        System.out.println(str + " :" + localVar.get());
        //Clear local variables in local memory
        localVar.remove();
    }

    public static void main(String[] args) {
        Thread t1  = new Thread(new Runnable() {
            @Override
            public void run() {
                //Set the value of the local variable in thread 1
                localVar.set("localVar1");
                //Call print method
                print("thread1");
                //Print local variables
                System.out.println("after remove : " + localVar.get());
            }
        });

        Thread t2  = new Thread(new Runnable() {
            @Override
            public void run() {
                //Set the value of the local variable in thread 1
                localVar.set("localVar2");
                //Call print method
                print("thread2");
                //Print local variables
                System.out.println("after remove : " + localVar.get());
            }
        });

        t1.start();
        t2.start();
    }
}

Operation results:

 

5, InheritableThreadLocal

The InheritableThreadLocal class is a subclass of ThreadLocal. In order to solve the problem that each thread can only see its own private value in the ThreadLocal instance, InheritableThreadLocal allows all child threads created by a thread to access the value of its parent thread.

Source code:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

As can be seen from the above code, the inheritablethreadlocal class inherits the ThreadLocal class and rewrites the childValue, getmap and createMap methods. When the createMap method is called (it needs to be called when the map obtained when the current thread calls the set method is null), it creates inheritablethreadlocal instead of threadlocals. Similarly, the getMap method returns when the caller thread invokes the get method, not threadLocals, but inheritableThreadLocal.

Let's see when the overridden childValue method is executed and how to let the child Thread access the local variable value of the parent Thread. Let's start with the Thread class

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    //Judge the legitimacy of the name
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;
    //(1) Get current thread (parent thread)
    Thread parent = currentThread();
    //Safety verification
    SecurityManager security = System.getSecurityManager();
    if (g == null) { //g: Current thread group
        if (security != null) {
            g = security.getThreadGroup();
        }
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }
    g.checkAccess();
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

    g.addUnstarted();

    this.group = g; //Set as current thread group
    this.daemon = parent.isDaemon();//Daemon thread or not (same as parent thread)
    this.priority = parent.getPriority();//Same priority as parent thread
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    //(2) If the inheritableThreadLocal of the parent thread is not null
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        //(3) Set inheritablethreadlocales in the child thread to inheritablethreadlocales of the parent thread
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    this.stackSize = stackSize;

    tid = nextThreadID();
}

In the init method, first (1) gets the current thread (Fu Xiancheng), then (2) determines whether the inheritableThreadLocals of the current parent thread is null, then calls createInheritedMap to create a new ThreadLocalMap variable using the inheritableThreadLocals of the parent thread as the constructor parameter, and assigns it to the child thread. The following are the construction methods of createinheritedmap method and threadlocalmap

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];

    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                //Call overridden method
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

Assign the value of the inheritablethreadlocals member variable of the parent thread to the new ThreadLocalMap object in the constructor. The inheritablethreadlocals assigned to the child thread after return. In short, the inheritablethreadlocals class saves the local variables into the inheritablethreadlocals variable of a specific thread by overriding the getMap and createMap methods. When the thread sets the variables through the set or get methods of the inheritablethreadlocals instance, the inheritablethreadlocals variable of the current thread will be created. When the parent thread creates a child thread, the constructor in ThreadLocalMap will copy the variable in the inheritablethreadlocals of the parent thread to the inheritablethreadlocals variable of the child thread.

6, Memory leakage caused by improper use of ThreadLocal from ThreadLocalMap

Analyze the internal implementation of ThreadLocalMap

As we know above, ThreadLocalMap is actually an Entry array, let's first look at the inner class of Entry

/**
 * Is a class inherited from WeakReference. The key actually stored in this class is
 * The weak reference to ThreadLocal and its corresponding value value (the value)
 * Is the value passed through the set method of ThreadLocal)
 * Because it is a weak reference, when the get method returns null, it means that the pit can be referenced
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** value It is bound to ThreadLocal */
    Object value;

    //k: The reference of ThreadLocal is passed to the constructor of WeakReference
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
//Construction method of WeakReference (public class WeakReference < T > extends reference < T >)
public WeakReference(T referent) {
    super(referent); //referent: reference to ThreadLocal
}

//Reference construction method
Reference(T referent) {
    this(referent, null);//referent: reference to ThreadLocal
}

Reference(T referent, ReferenceQueue<? super T> queue) {
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

In the above code, we can see that the reference k of the current ThreadLocal is passed to the constructor of WeakReference, so the key in ThreadLocalMap is the weak reference of ThreadLocal. When a thread calls the set method of ThreadLocal to set variables, the ThreadLocalMap of the current thread will store a record. The key value of this record is the weak reference of ThreadLocal, and the value is the value set through set. If the current thread always exists and does not call the remove method of the ThreadLocal, if there are references to ThreadLocal elsewhere at this time, there will be references to ThreadLocal variables and value objects in the ThreadLocalMap in the current thread, which will not be released, resulting in memory leakage.

Considering that this ThreadLocal variable has no other strong dependencies, if the current thread still exists, because the key in the ThreadLocalMap of the thread is a weak reference, the weak reference of the ThreadLocal variable in the ThreadLocalMap of the current thread will be recycled during gc, However, the corresponding value still exists, which may cause memory leakage (because at this time, there will be an entry item with null key but non null value in ThreadLocalMap).

Summary: the key of the Entry in threadlocalmap uses a weak reference to the ThreadLocal object. If there is no dependence on ThreadLoca elsewhere, the ThreadLocal object in threadlocalmap will be recycled, but the corresponding will not be recycled. At this time, there may be items with null key but non null value in the Map, This requires that the remove method be called in time after the actual use to avoid memory leakage.

Topics: Java Multithreading Memory Leak