ThreadLocal thread isolation

ThreadLocal is an internal thread storage class under the java.lang package. It can store data in the thread memory. After the data is stored, only the specified thread can get the stored data to realize thread isolation.

ThreadLocal creates a copy of the variable in each thread, so each thread can access its own internal copy variable. It can be used anywhere within the thread, and threads do not affect each other

ThreadLocal provides local variables in the process. Different threads will not work within the life cycle of the process, reducing the complexity of transmitting some public variables between multiple functions or components in the same process

In multithreading and distribution, we can pass public variables through Threadlocal in the same thread and different components. The variables of each thread are independent and will not affect each other

internal structure


Each Thread holds a ThreadLocalMap object. The key of this Map is the Threadlocal instance itself, and value オ is the real value object to be stored. Each new Thread thread will instantiate a ThreadLocalMap and assign it to the member variable threadLocals. If threadLocals already exists, the existing object will be used directly.

  1. There is a map (ThreadLocal map) inside each thread process. When the thread is destroyed, the map will also be destroyed
  2. (the Map stores the Threadlocall object (key) and the variable star copy (value) of the thread.)
  3. The map inside the Thread is maintained by Threadlocal, which is responsible for obtaining and setting the variable value of the process from the map
  4. For different threads, each time this value is obtained, other threads cannot obtain the copy value of the current thread, forming the isolation of copies


Null parameter construction

  • Threadlocal create Threadlocal object

The corresponding value of the current thread can be obtained through the get and set methods.

  • public void set()

This method checks the current calling thread, and uses the Thread.currentThread() value of the thread as the key to save the specified value. Bind the variable to the thread.

  • public Object get()

This method checks the current calling thread and acquiescence the Thread.currentThread() value of the thread as the key to get the specified value saved by the thread binding.

Remove the local variable bound by the current procedure

  • public void remove()

ThreadLocal is mainly used for:
1. When transferring objects across layers, using ThreadLocal can avoid multiple transfers and break the constraints between layers.

2. Data isolation between threads

3. Conduct transaction operation, which is used to store thread transaction information.

4. Database connection, Session management

Get method to get the thread local copy variable

public T get() {
    Thread t = Thread.currentThread();
    //Gets the threadLocalMap for this thread object
    ThreadLocalMap map = getMap(t);
    //map exists
    if (map != null) {
        //Take the current Threadlocal as the key and call getentry to obtain the corresponding storage entity
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            //Get the value corresponding to the storage entity e, that is, the value corresponding to this Threadlocal of the current thread we want
            T result = (T)e.value;
            return result;
        }
    }
    //Initialization: there are two situations in which the current code is executed
    //The first one: the map does not exist, which means that this thread does not have a protected Threadlocalmap object
    //The second case: the map exists, but there is an entry associated with the current Threadloca1
    return setInitialValue();
}
private T setInitialValue() {
        //Call initialvalue to get the initialized value. This method can be overridden by subclasses. If it is not overridden, nul1 will be returned by default
        T value = initialValue();
        Thread t = Thread.currentThread();
    //Gets the Threadlocalmap object maintained in this thread object
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //If it exists, use map and set to set the entity entry
            map.set(this, value);
        } else {
            //If the Threadlocalmap object does not exist in the current Thread thread, call createmap to initialize the Threadlocalmap object
           //T (the current thread) and value (the value corresponding to t) are stored in the ThreadLocal map as the first entry
            createMap(t, value);
        }
    //Register TerminatingThreadLocal
    //TerminatingThreadLocal is another extension of ThreadLocal. The value associated with the ThreadLocal will be specially processed before the end of the thread. The processing method depends on the callback method threadTerminated(T value)
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

The set method sets the local copy of the thread

public void set(T value) {
     //Get current thread
     Thread t = Thread.currentThread();
     //Gets the threadLocalMap maintained by the thread object
     ThreadLocalMap map = getMap(t);
     //If there is a map, set it directly. If there is no map, create a map and set this entry
     if (map != null)
         map.set(this, value);
     else
         //1) The Threadlocalmap object does not exist for the current Thread thread
         //2) Then use createMap to initialize the Threadlocalmap. Object
         //3) T (the current future) and value (the value corresponding to t) are stored in the ThreadLocal map as the first entry
         createMap(t, value);
}
//Gets the Threadlocalmap corresponding to the current Thread thread
 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
  }
//Create a Threadlocalmap that the current Thread should maintain
//  t the value of the first entry stored in the map by the current thread's firstvalue
 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

The key value of ThreadLocalMap is the ThreadLocal object set by the set method, and there can be multiple ThreadLocal variables, so it is saved in the map

ThreadLocal itself does not store values. It is just used as a key to let the thread obtain values from ThreadLocalMap.

Remove method to remove the thread local copy variable

public void remove() {
 ThreadLocalMap m = getMap(Thread.currentThread());
 if (m != null)
     m.remove(this);//Delete the entry corresponding to the map
}

Let each Thread object own a Map. The Key of this Map is the current ThreadLocal object, and Value is the Value of the local Thread variable. Compared with the implementation of locking, this can improve performance. In fact, it is an idea of changing time for space

ThreadLocalMap

threadLocals is a data structure of ThreadLocal.ThreadLocalMap type. It is defined in the ThreadLocal class as an internal class, and the key value pairs are saved in a way of WeakReference.

Member variable

//The initial volume must be the whole lifting of 2
private static final int INITIAL_CAPACITY 16:
//The definitions of table and entry classes for storing data are analyzed below
//Similarly, the array length must be an entire degree of 2
private Entry[] table;
//The number of entries in the array can be used to judge whether the current usage of tablel exceeds the national value
private int size =0
//The value for capacity expansion. Capacity expansion is performed when the table usage is greater than it
private int threshold; //Default to 0

Similar to Hashmaps, INITIAL CAPACITY represents the INITIAL CAPACITY of the Map; Table is an array of Entry type, which is used to store data; Slze represents the number of stores in the table; Threshold represents the threshold value of the corresponding size when capacity is required

Entry

/*
Entry Following the weakreffrence, the well uses Threadloca as the key
 If the key is null (entry. Get (o) = = nul1), it means that if the key is no longer referenced
 Therefore, the entry can also be cleared from the tablel at this time
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

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

In Threadlocal map, Entry is also used to save K-V structure data. However, the key in the Entry can only be a Threadlocal object, which is limited in the construction method
In addition, Entry inherits Weakreference, that is, key (Threadlocal) is a weak reference, which aims to unbind the life cycle of Threadlocal object and thread life cycle

Thread threadLocal ThreadLocalMap relationship:

  • Each thread has threadLocals, whose type is threadLocal.ThreadLocalMap. threadLocal is the manager of the threadLocals attribute in the thread.
  • The threadLocalMap object is stored in the thread object. Its name is threadLocals. It is an array. The data is an Entry type. The maintainer has one or more entries. The key of the Entry is a weak reference to the ThreadLocal instance, and the value is an object type, which is a thread specific variable
  • When accessing replica data through threadLocal, in fact, theadLocalMap is obtained through thread, and replica data is obtained through the key of threadLocalMap
  • The key of threadLocalMap is the weak reference of threadLocal object. Its purpose is to better recycle threadLocal. If the key is set to strong reference, the threadLocal cannot be recycled

ThreadLocal and Synchronized

ThreadLocal and Synchronized are used to solve the access conflict of the same variable in multiple threads,

The difference is that Synchronized solves access conflicts through thread waiting and sacrificing time. It only provides a variable for different threads to queue up for access to ensure the synchronization of access resources between multiple threads

ThreadLocal provides a copy of variables for each thread by exchanging space for time, so as to achieve simultaneous access without interference
Compared with Synchronized, ThreadLocal has the effect of thread isolation. In multithreading, the data between each thread is isolated from each other, which can enable concurrent execution between threads

Hash conflict resolution

The constructor first creates an entry array with a length of 16, then calculates the index corresponding to the firstKey, stores it in the table, and sets the size and threshold

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    //Calculate index
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);//Set initial value
    size = 1;
    setThreshold(INITIAL_CAPACITY);//Set threshold
}

Calculate index

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


firstKey.threadLocalHashCode

private final int threadlocalhashcode -nexthashcodeo
private static int nexthashcodeo
return pexthashcode. getandadd(HASH_INCREMENT);
//Atomicinter is an Integer beauty that provides atomic operation. It provides addition and subtraction in a thread safe way. It is suitable for high platform and estrous brother
private static Atomicinteger nexthashcode = new Atomicinteger O:
//Special hash value
private static final int HASH_INCREMENT =0x61c88647

Here, an Atomiclntegers type is defined. Each time the current value is obtained, HASH INCREMENT is added=
0x61c88647 this value is related to the desired Bona column (Golden partition number). Its main purpose is to make the hash code evenly distributed in the n-th power array of 2, that is, the Entry table. This can avoid hash conflict as much as possible

& (INITIAL_CAPACITY - 1)

When calculating the hash, the hash code & (size-1) algorithm is adopted, which is equivalent to a more efficient implementation of the modular operation hashcode%size. It is precisely because of this algorithm that we find that the size must be the whole scene of 2, which can also reduce the number of hash conflicts on the premise that the prime reference does not cross the boundary

set method

private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);//Calculate index
    //threadLocakMap uses linear probing to find elements  
    /*
    This method detects the next address at a time until there is an empty address. If the empty address cannot be found in the whole space, overflow will occur
    Suppose the length of the current table is 16, that is, if the hash value of the calculated key is 14, if there is already a value on table[14]
  If the key is different from the current key, a hash conflict occurs. At this time, 14 is added by 1 to get 15. Take table[15] for judgment. If the conflict still occurs, it will return to 0, take table[0], and so on until it can be inserted
    */
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        // The key corresponding to Threadlocal exists and directly covers the previous value
        if (e.refersTo(key)) {
            e.value = value;
            return;
        }
        //The key is nul1, but the value is not nul1, indicating that the previous Threadlocal Liu Xiang has been recycled   
        if (e.refersTo(null)) {
            //Replace old elements with new ones. This method does a lot of garbage cleaning to prevent memory leakage
            replaceStaleEntry(key, value, i);
            return;
        }
    }
   // The key corresponding to Threadlocal does not exist and no old element was found. Create a new Entry at the position of the empty element
    tab[i] = new Entry(key, value);
    int sz = ++size;
    /*
     cleansomeslots Used to clear the elements of e.get()==nu11,
     The object associated with this number key has been recycled, so the entry (table [index]) can be set to null
     If no entry is cleared and the current usage reaches the load factor definition (2 / 3 of the length), rehash (perform a full table scan cleanup)
    */
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
 }

A first calculates the index 1 according to the key, and then finds the Entry on the bit
B. If the Entry already exists and the key is equal to the passed in key, then a new value is directly assigned to the Entry
C. If the Entry exists but the key is nul, call replacesaleentry to replace the Entry with an empty key
D. Keep checking the loop until you encounter a nul. At this time, if you haven't return ed during the loop, create a new one in the nul bit
Entries, wells and inserts, and slze increases by 1
Finally, call cleansomeslots, clean up key for null Entry, finally return to clean up Entry, then determine whether size is > = threshold reaches rehash condition, if it reaches, then will call rehash function to perform a full table scan cleaning.

Weak references and memory leaks

Basic reference

There are four types of references in Java: strong, soft, weak and virtual

Strong reference

The most common common common object reference, as long as there is a strong reference to an object, it can indicate that the object is still alive, and the garbage collector will not recycle this object

Weak reference

Once the garbage collector finds an object with only weak references, it will reclaim its memory regardless of whether the current memory space is sufficient or not

Soft reference

It is used to describe some useful but not necessary objects, which are represented by the java.lang.ref.SoftReference class in Java. For objects associated with soft references, the JVM will recycle the object only when there is insufficient memory. Therefore, this can be well used to solve the problem of OOM, and this feature is very suitable for caching: such as web page caching, image caching, etc

Phantom reference

Unlike previous soft and weak references, virtual references do not affect the life cycle of objects. In Java, it is represented by the java.lang.ref.PhantomReference class. If an object is associated with a virtual reference, it may be recycled by the garbage collector at any time, just as no reference is associated with it. Virtual references are mainly used to track the activity of objects being garbage collected.

The virtual reference must be associated with the reference queue. When the garbage collector prepares to recycle an object, if it finds that it still has a virtual reference, it will add the virtual reference to the associated reference queue. The program can know whether the referenced object will be garbage collected by judging whether a virtual reference has been added to the reference queue. If the program finds that a virtual reference has been added to the reference queue, it can take the necessary action before the memory of the referenced object is reclaimed.

Memory leak

  • Memory overflow memory overflow. There is not enough memory for the applicant
  • Memory leak: memory leak refers to the heap memory that has been dynamically allocated in the program. For some reason, the program does not release or cannot release, resulting in system memory leakage
    Waste, resulting in serious consequences such as program speed reduction and even system period. The accumulation of industrial memory leakage will eventually overflow

If the key uses strong references

key uses a weak reference

Prerequisites for memory leakage when ThreadLocal:

  1. The ThreadLocal reference is set to null, and there are no set, get, or remove operations.
  2. The thread runs all the time without stopping. (line pool)
  3. Garbage collection was triggered. (Minor GC or Full GC)

ThreadLocal is a weak reference, but value is a strong reference. When the key is null, ThreadLocal will be garbage collected; However, at this time, the ThreadLocalMap life cycle is the same as that of the Thread. Only after the current Thread ends, all resources related to the current Thread will be recycled by GC. Unless deleted manually, it will not be recycled. At this time, a strong reference chain Threadref – > Thread – > ThreadLocalMap – > entry (key, value) appears. Value will not be recycled, and this value will never be accessed, resulting in value memory leakage.


Since both strong and weak references will leak memory, why use weak references?

In fact, in the set/getentry method in Threadlocal map, it will judge that the key is null (that is, Threadlocal is null),

If it is null, value will be set to null
This means that if the thread is still running after using Threadlocal, even if you forget to call the remove method, the weak reference can provide more protection than the strong reference: the Threadlocal of the weak reference will be recycled, and the corresponding value will be cleared when the Threadlocal map calls set. Get and any method in remove the next time, so as to avoid memory leakage

However, this is only one more layer of guarantee. If the memory leak is solved forever, please call the remove method after use

Root cause

The life cycle of threadlocalmap is as long as that of Thread. As long as the Thread runs all the time, if the corresponding key is not manually deleted, because value is a strong reference (even if the key is a weak reference), the value of entry cannot be recycled. Therefore, the strong reference chain Threadref – > Thread – > threadlocalmap – > entry (key, value) always exists, resulting in memory leakage of value

Solution: after using ThreadLocal, execute the remove operation to avoid memory overflow

When using ThreadLocal, follow the following two small principles

  • ThreadLocal is declared as private static final. Private and final try not to let others modify or change references,
  • Static is represented as a class attribute and will be recycled only at the end of the program. Be sure to call the remove method after ThreadLocal is used. The simplest and most effective way is to remove it after use.

InheritableThreadLocal

The ThreadLocal class cannot provide the child thread to access the local variables of the parent thread, while the InheritableThreadLocal class can do this

public class Test {

    public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();

    public static void main(String args[]) {
        threadLocal.set(new Integer(456));
        Thread thread = new MyThread();
        thread.start();
        System.out.println("main = " + threadLocal.get());
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("MyThread = " + threadLocal.get());
        }
    }
}

InheritableThreadLocal class overrides ThreadLocal's three functions:

 /**
     * This function is used when the parent thread creates a child thread and copies the InheritableThreadLocal variable to the child thread
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }
    /**
     * Because getMap is rewritten, when operating InheritableThreadLocal,
     * Only the inheritableThreadLocals variable in the Thread class will be affected,
     * No longer related to threadLocals variable
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * Similar to getMap, when you operate InheritableThreadLocal,
     * Only the inheritableThreadLocals variable in the Thread class will be affected,
     * No longer related to threadLocals variable
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

 

The root cause for the child Thread to access the local variables of the parent Thread: the InheritableThreadLocal of the parent Thread is copied when constructing the Thread object

public class Thread implements Runnable {
      //The default constructor will call the init method for initialization
      public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }

//This method will eventually be called
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name.toCharArray();
// parent is the current thread, that is, new Thread() is called; Method's thread
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            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;
//Here, the properties of whether the parent thread is a background thread and the priority of the parent thread will be inherited
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        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);
//Here is the key point. When the inheritablethreadlocales of the parent thread is not empty, the ThreadLocal.createInheritedMap method will be called, and the inheritablethreadlocales of the parent thread will be passed in. 
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

}

As long as the parent thread constructs the child thread (calls new Thread()), the inheritableThreadLocals variable is not empty. The newly generated child thread will copy the objects of the inheritableThreadLocals variable of the parent thread to the inheritableThreadLocals variable of the child thread through the ThreadLocal.createInheritedMap method. This completes the inheritance and transfer of variables between threads.

The reason why InheritableThreadLocal can complete the transfer of variables between threads is that the value in the inheritableThreadLocals object is copied at the time of new Thread().

The value in InheritableThreadLocal obtained by the child thread through inheritance has the same reference as the value of InheritableThreadLocal in the parent thread. If the parent and child threads want to implement without affecting their respective objects, they can override the childValue method of InheritableThreadLocal.

Tags: Java JUC ThreadLocal

Posted on Sun, 03 Oct 2021 13:07:43 -0400 by guestabc