Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This paper introduces the function, principle, source code and application of ThreadLocal in Java, and introduces the principle and solution of ThreadLocal memory leak.

1 Overview of ThreadLocal

1.1 Introduction to ThreadLocal

public class ThreadLocal< T > extends Object

ThreadLocal comes from JDK1.2 and is in the java.lang package. A ThreadLocal can provide local variables within a thread that operate for the lifetime of the thread. ThreadLocal is also called thread-local variables/thread-local storage.

In fact, the ThreadLocal class alone does not store anything. The actual collection of data is stored in the threadLocals variable for each Thread. ThreadLocal defines the structure of the collection and provides a set of operations. The following source code analysis will talk about!

ThreadLocal is a utility class that operates on threadLocals of individual threads.

ThreadLocal functions and purposes:

  1. For intra-thread data sharing, consider ThreadLocal when some data is thread-scoped and different threads have different copies of the data, that is, data is isolated between threads. That is, for the same program code, multiple modules running in the same thread share one piece of data, and running in another thread share another piece of data.
  2. It is convenient for data transfer under the complex logic of the same thread. Sometimes tasks in a thread are too complex, and we need certain data to run through the whole thread execution process, which may involve data transfer between different classes/functions. In this case, Threadlocal is used to store data. Within the thread, just use the get method to get the data stored in the thread.

1.2 Synchronization and ThreadLocal

Synchronization and ThreadLocal are two ways to solve the problem of data access in multithreading. The former is the idea of data sharing, while the latter is the idea of data isolation. Synchronization is a time-for-space idea, and ThreadLocal is a space-for-time idea. The former only provides a variable, so that different threads queue to access, serialization; The latter provides a list of variables for each thread, so they can be accessed simultaneously without affecting each other.

Threadlocal is not a substitute for synchronization. Note that Threadlocal is not designed to solve the problem of multithreaded access to shared objects. The set() function of ThreadLocal is used to render objects to threadLocals, which other threads do not need to access and cannot access. ThreadLocals and values in each thread are different objects. Threadloocal is used for variable isolation, which means ThreadLocal is for attributes that don’t need to be shared!

1.3 Main API methods and use cases

The ThreadLocal class has four main methods that can be called:

  1. Void set(T value) : saves the value.
  2. T get() : get the value;
  3. Void remove() : removes a value;
  4. InitialValue () : returns the initialValue of this thread-local variable, which is designed to be inherited by subclasses. This method is a deferred call, executed when a thread first calls GET () (and set is not called). The default implementation in ThreadLocal returns null directly.

ThreadLocal implements data sharing within a thread.

public class ThreadLocalTest {
    /** * Global ThreadLocal objects reside in the heap, which is shared by threads, while the method stack, which is private to each thread, is */
    static ThreadLocal<String> th = new ThreadLocal<>();

    public static void set(a) {
        // Set the value to the name of the current thread
        th.set(Thread.currentThread().getName());
    }

    public static String get(a) {
        / / get the value
        return th.get();
    }


    public static void main(String[] args) throws InterruptedException {
        System.out.println("Trying to get value in main thread :" + get());
        // Set the value to the thread name in the main thread
        set();
        // The main thread tries to get a value
        System.out.println("Try to get value again in main thread :" + get());
        // Start a sliver
        Thread thread = new Thread(new Th1(), "child");
        thread.start();
        // The main thread waits for the child thread to finish executing
        thread.join();
        System.out.println("Wait for the child thread to complete, then try again in the main thread to get the value :" + get());
    }

    static class Th1 implements Runnable {
        @Override
        public void run(a) {
            System.out.println("Trying to get value in child thread :" + get());
            // Set the value in the child thread. The value is the thread name
            set();
            System.out.println("Trying to get value again in child thread :"+ get()); }}}Copy the code

The results are as follows:

Try to get value in main thread:nullThe main thread attempts to fetch a value again:nullRetry in child thread :child waits for the child thread to complete, retry in main thread :mainCopy the code

First set the value and then get it to get “main”. And then I start the child thread, and inside the child thread, I get null, I set the value, I get child. ThreadLocal limits the scope of the variable to the local thread and makes it inaccessible to other threads.

Note that because the example does not call the remove method at the end of the case, in actual use, it should be called after the use of the remove method, the principle will be explained later!

2 Principles of ThreadLocal

2.1 Basic Relationship

The ThreadLocal class defines an inner class, ThreadLocalMap. ThreadLocalMap is the actual container for storing data, and its underlying layer is actually a hash table.

Each Thread Thread internal definitions have a ThreadLocal ThreadLocalMap threadLocals variable types, so, noninterference ThreadLocalMap between threads. The ThreadLocalMap held by the threadLocals variable is initialized when ThreadLocal calls the set or get methods.

ThreadLocal also provides methods that get and set the value of a thread’s variable into the ThreadLocalMap variable of the current thread, equivalent to a utility class.

When a ThreadLocal is used to set a value in a thread’s method, the ThreadLocal object is added to the thread’s internal ThreadLocalMap, where the key is the ThreadLocal object and the value can be of any type or value. When a ThreadLocal object is used to fetch a value from a thread’s method, the corresponding value is retrieved from the thread’s ThreadLocalMap with the ThreadLocal object as the key.

ThreadLocal defines the structure of a ThreadLocalMap and provides methods for manipulating it:

    public class ThreadLocal<T> {
        / /...
        static class ThreadLocalMap {
            / /...
        }

        When a ThreadLocal is created, a ThreadLocalMap is not initialized, and may be initialized in a set or get method. * /
        public ThreadLocal(a) {}}Copy the code

Each Thread object holds a reference variable of type ThreadLocalMap that holds thread-local variables. Key is the ThreadLocal object and value is the data to be stored.

public class Thread implements Runnable {
    /* The thread-local value associated with this thread. This ThreadLocalMap is defined in the ThreadLocal class and is used in the Thread class */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    / /...........................
}
Copy the code

ThreadlocalMap: threadlocalMap: threadlocalMap

2.2 Basic Structure

ThreadLocal defines the structure of a ThreadLocalMap.

ThreadLocalMap is also a key-value hash table, but ThreadLocalMap does not implement a Map interface. It has an internal table array of Entry types for storing nodes. Entry node is used to store key and value data, and inherits WeakReference.

By hashing the ThreadLocal object, the bucket bits of the ThreadLocal object in the Entry array can be obtained to find the unique Entry. If a hash collision occurs, ThreadLocalMap uses a “linear probe” to resolve hash collisions, unlike the “chain-address” method used by HashMap and Hashtable, because it is simpler to implement and takes up less space. And generally a ThreadLocalMap doesn’t hold a lot of data! For details on the principles of hash tables and various methods to resolve hash conflicts, you can see this article: Data structure-hash table (hash table) principles and Java code implementation.

The 16-length internal table array is initialized at the same time as the ThreadLocalMap object is created. The capacity expansion threshold is len * 2/3, and the capacity expansion increment is double the original capacity.

The ThreadLocalMap object in the thread remains null when the ThreadLocal setting is not used to fetch the value.

/** * The inner class of ThreadLocalMap */
static class ThreadLocalMap {

    /** * The initialization capacity of the table array, */
    private static final int INITIAL_CAPACITY = 16;
    // An array of data that will be initialized when ThreadLocalMap is created. The size must be 2^N
    private Entry[] table;
    // The capacity expansion threshold is len * 2/3
    private int threshold;

    /** * Internal node object, it seems that there is no "key" field where, in fact, the property storing K is located in the parent class of WeakReference Reference, named referent, that is, when the property reuse * inserts data, Hash the key (threadLocal object) to find the bucket of the table array where the Entry should be stored. * However, it may cause hash conflicts. Linear detection is used to resolve conflicts, so linear backward lookup is required. * /
    static class Entry extends WeakReference<ThreadLocal<? >>{
        / / stored value
        Object value;
        / / the constructorEntry(ThreadLocal<? > k, Object v) {// Call the parent constructor, passing in key, where k is wrapped as a weak reference
            // In fact, the property storing K is located in the parent class Reference of WeakReference, named referent, that is, attribute reuse
            super(k); value = v; }}}Copy the code

2.3 set method

The set method is provided by ThreadLocal and is used to store data as follows:

  1. Gets the member variable threadLocals of the current thread.
  2. If threadLocals is not null, the set method is called, and the method ends.
  3. Otherwise, the createMap method is called to initialize threadLocals, store the data, and the method ends.
/** * ThreadLocal, open to external calls to the data store method **@paramValue Specifies the data to be stored */
public void set(T value) {
    // Note that we first get the current thread t
    Thread t = Thread.currentThread();
    //1.1 Then pass in t to get the threadLocals of the current t thread via the getMap method
    ThreadLocalMap map = getMap(t);
    // If map exists, store data
    if(map ! =null)
        // This represents the current ThreadLocal object, and value represents the value
        map.set(this, value);
    else
        // If not, build ThreadLocalMap belonging to the current thread and store the data
        createMap(t, value);
}

/** ** method in 1.1 ThreadLocal to get the specified thread's threadLocals **@paramT specifies the thread *@returnT threadLocals * /
ThreadLocalMap getMap(Thread t) {
    //t represents the current thread and gets the thread's threadLocals property, which is a ThreadLocalMap (null by default)
    return t.threadLocals;
}


/** * the method in 1.2 ThreadLocal that builds a ThreadLocalMap object and assigns a value **@paramT Current thread *@paramFirstValue Specifies the value to store */
void createMap(Thread t, T firstValue) {
    // This is the method in ThreadLocal. This refers to the current ThreadLocal object
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}


/** * The constructor in ThreadLocalMap that creates a new ThreadLocalMap object **@param firstKey   key
 * @param firstValue value
 */ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// Create an array of tables with an initial capacity of 16
    table = new Entry[INITIAL_CAPACITY];
    // Find the array bucket bits using the threadLocalHashCode property of the ThreadLocal object & 15
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // This is where the element is stored. Since the object is newly created, there is no hash conflict
    // As the constructor analyzes in the "basic structure" section, the key is ultimately wrapped as a weak reference.
    table[i] = new Entry(firstKey, firstValue);
    //size set to 1
    size = 1;
    //setThreshold sets the capacity expansion threshold
    setThreshold(INITIAL_CAPACITY);
}

/** * method in ThreadLocalMap to set the capacity expansion threshold **@paramLen Array length */
private void setThreshold(int len) {
    // The length of the array is 2/3
    threshold = len * 2 / 3;
}
Copy the code

2.3.1 Internal set method

If threadLocals of the current t thread is not null, a private set method is called to store data. This method is one of the core methods of ThreadLocal, and it is relatively complex, with the following steps:

  1. Hash algorithm is used to calculate the bucket bit I stored by the current key, and obtain the element E of I.
  2. If e is not empty, it indicates that hash conflict occurs. Replace or store data with linear detection method:
    1. If e’s key is equal to the specified key (using the == comparison), the method ends by replacing value.
    2. Otherwise, if e’s key is null, it is invalid. The replaceStaleEntry call cleans up the invalid data from the index and stores the new data.
      1. If an entry with the same key is found, it is placed in the invalid bucket bit, value is set to the new value, and the method ends.
      2. If no entry with the same key is found, the method ends by placing the entry in the invalid slot.
      3. When the replaceStaleEntry method is called, the new data must be stored in ThreadLocalMap without further steps.
    3. Otherwise, the nextIndex method takes the nextIndex and assigns it to I. If the node e at that position is null, the loop ends; otherwise, the next loop proceeds.
  3. In this step, value is not replaced or invalid data is not cleaned. Instead, an empty bucket bit I is found and a new entry is directly inserted into this position. At this point, there must be valid nodes between the initial bucket I and the current bucket.
  4. After storing the elements, call cleanSomeSlots to do a partial cleanup of invalid nodes. If no key is removed (return false) and the current table size is greater than or equal to the threshold, call rehash.
  5. The rehash method calls the expungeStaleEntries() method for a full table sweep sweep. If the table size is still greater than or equal to (threshold – threshold / 4) after expungeStaleEntries are complete, call the resize method for expansion.
  6. The resize method doubles the capacity and migrates nodes.

ThreadLocalMap uses == to compare keys. ThreadLocalMap resolves Hash collisions by simply adding or subtracting 1 step size (linear probing) to find the next adjacent location. When looking forward to the head of the array or looking backwards to the tail of the array, the next position is either the tail of the array or the head of the array.

/** * Set method in ThreadLocalMap, used to store data. * *@paramKey ThreadLocal object *@paramThe value value * /
private void set(ThreadLocal
        key, Object value) {
    // TAB holds array references
    Entry[] tab = table;
    //len holds the degree of the array
    int len = tab.length;
    The threadLocalHashCode attribute of ThreadLocal calculates the bucket I */ of the array corresponding to the key (ThreadLocal object)
    int i = key.threadLocalHashCode & (len - 1);
    /* * 2 select Entry E from index I. If e is not null, a hash conflict has occurred. * Determine whether the two keys are equal, that is, whether value replacement is required. If the two keys are equal, replace value. * otherwise, judge whether the key of the obtained e is null. If the obtained ThreadLocal is null, it means that the WeakReference (WeakReference) is recovered (because Entry inherits WeakReference). The replaceStaleEntry method is called to erase that location or any other invalid Entry and reassign the value. When that is done, the replaceStaleEntry method returns, This is to prevent memory leaks * otherwise, determine whether the location I is null, that is, there is no node, if null, create a node at that location and insert, end. * Otherwise, I = nextIndex(I, len), try the next loop. * If the index is null and the next node is null, insert a new node at that position. If the index is null, insert a new node at that position. * * This is how ThreadLocalMap resolves hash collisions, namely, open addressing -- linear probing: NextIndex () is used to loop array indexes, that is, if initial I is 15 and length is 16, nextIndex will return 0, if initial I is 1 and length is 16, nextIndex will return 2. * This makes it easier to use space in front of the initial index * */
    for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) {
        /* Get the Entry key, the original ThreadLocal object, which is the parent class Reference method */ThreadLocal<? > k = e.get();/* If the ThreadLocal and the ThreadLocal are the same object, then the value is replaced
        if (k == key) {
            e.value = value;
            return;
        }
        /* * If ThreadLocal is null, the WeakReference is reclaimed (because Entry inherits WeakReference), * indicates that the ThreadLocal must have no strong reference outside, and the Entry becomes garbage. Erasing the Entry at that location, reassigning, and terminating the method is to prevent memory leaks */
        if (k == null) {
            /* * From there, continue to look for the key and clean up as many invalid slots as possible. * During the replaceStaleEntry process, if the key is found, a swap is done to place it in that invalid slot. * During replaceStaleEntry, the key was not found and entry * */ was placed in place of the invalid slot
            replaceStaleEntry(key, value, i);
            return; }}/* * Insert a new Entry into position I * to ensure that there is a node between the original position I and the current position. * * /
    tab[i] = new Entry(key, value);
    //size increases by 1, using sZ record
    int sz = ++size;
    /* 3 Try to clean up the garbage, then determine if you need to expand the garbage, if necessary * after storing the elements, call cleanSomeSlots again to clean up the garbage, if the key is not cleared (return false) * and the current table size is greater than or equal to the threshold, If expungeStaleEntries are complete and the table size is still greater than or equal to (threshold - Threshold / 4), then call the resize method to expand the capacity * resize method will double the capacity, and complete the node transfer * */
    if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }/** * gets the next index of I within the index range of length, loop */
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

/** * get the last index of I in the index range of length, loop */
private static int prevIndex(int i, int len) {
    return ((i - 1> =0)? i -1 : len - 1);
}

private void rehash(a) {
    expungeStaleEntries();
    // Use lower threshold for doubling to avoid hysteresis
    if (size >= threshold - threshold / 4)
        resize();
}

Copy the code

2.3.2 Hash algorithm

In the set method, we can find that the hash algorithm of ThreadLocalMap is:

int i = key.threadLocalHashCode & (len – 1);

Since len must be a power of 2, the bit operation above can be converted to key.threadLocalHashcode % len, so ThreadLocalMap’s hash algorithm is also a modulo algorithm, because the remainder must be smaller than the divisor. The bucket bits must be between [0, len-1], which is within the index range of the underlying array.

ThreadLocalHashCode = threadLocalHashCode = threadLocalHashCode = threadLocalHashCode = threadLocalHashCode In fact, the threadLocalHashCode property is so interesting that we’ll have to look at the ThreadLocal source code!

public class ThreadLocal<T> {
    /** * Next hashCode * Note: This is a static property, so it will only be initialized when ThreadLocal's class is first loaded for class initialization, which is obviously 0. * /
    private static AtomicInteger nextHashCode = new AtomicInteger();

    /** * The threadLocal object's hashcode is not obtained through the hashcode method, which has its own calculation rules * as you can see, it is the return value of calling nextHashCode()
    private final int threadLocalHashCode = nextHashCode();

    /** * Each threadLocal object uses this method to get its own hashcode */
    private static int nextHashCode(a) {
        // Internally use the getAndAdd method of the nextHashCode object
        // The method returns the current value and then increments the current value with the specified value, in this case HASH_INCREMENT
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    /** * hash increment, as the name implies, is the increment of the hash value */
    private static final int HASH_INCREMENT = 0x61c88647;
}
Copy the code

Combining the above attributes and methods, we finally understand:

When a ThreadLocal instance is first created, the ThreadLocal class is loaded, nextHashCode initializes to 0, and then the threadLocalHashCode attribute of the object is initialized. After the class object is created, The nextHashCode method is automatically called, adding the value of nextHashCode as its own hashCode and adding HASH_INCREMENT to the value of the nextHashCode object, obviously as the hashCode value of the next ThreadLocal instance.

That is, each ThreadLocal instance uses the nextHashCode value at the time it was created as its own hashCode, and then increments the nextHashCode value with HASH_INCREMENT as the hashCode of the next ThreadLocal instance.

0 x61c88647 is hexadecimal number, converted to a decimal is 1640531527, in fact, the selection of the hash value of the incremental and Fibonacci hashing, golden ratio (www.javaspecialists.eu/archive/Iss…

2.4 the get method

For different threads, variable values are obtained from the internal threadLocals of the local thread each time. Other threads cannot obtain the values of the current thread, forming variable isolation. The steps are as follows:

  1. Gets the member variable threadLocals of the current thread
  2. If threadLocals is not empty, call the getEntry method to try to find and return node E:
    1. If e is not null, the value of e is found, and the method ends
    2. If e is null, it was not found and the method continues.

At this point, either threadLocals is empty, or e is not found. Call the setInitialValue method, set an entry for the key from the current ThreadLocal object, and return value.

/** * the get method in ThreadLocal is open to external calls to **@returnThe current ThreadLocal object of the current thread stores the value */
public T get(a) {
    // Get the current thread
    Thread t = Thread.currentThread();
    // Get the threadLocals object for the current thread
    ThreadLocalMap map = getMap(t);
    // If the map is not null, it is initialized
    if(map ! =null) {
        // Get the corresponding Entry node from the map, passing this to represent the current ThreadLocal object
        ThreadLocalMap.Entry e = map.getEntry(this);
        // If e is not null
        if(e ! =null) {
            // Get and return the value
            T result = (T) e.value;
            returnresult; }}Otherwise, if map is null, or e is null
    // Then return null or custom initial value
    return setInitialValue();
}
Copy the code

Against 2.4.1 getEntry method

A method inside ThreadLocalMap that attempts to obtain the corresponding Entry node, based on the key. The steps are as follows:

  1. Calculate the bucket position according to the key;
  2. Get the bucket node e, if e is not null and the key of e is equal to the specified key (using == comparison), then return e, end of the method;
  3. Otherwise, the getEntryAfterMiss method is called for a step length linear probe search, and expungeStaleEntry is called for cleaning up every invalid node encountered during the search. If found, the found entry is returned; If no bucket bits are found (empty bucket bits detected), null is returned.
/** * An internal method of ThreadLocalMap that obtains the corresponding Entry node ** based on the key@param key key
 * @returnEntry node, return null */ if not found
private Entry getEntry(ThreadLocal
        key) {
    // Calculate the bucket position based on the key
    int i = key.threadLocalHashCode & (table.length - 1);
    // Get Entry node E
    Entry e = table[i];
    /* If e is not nul and e internal key is equal to the current key (ThreadLocal object) */
    // We can see that key equality is directly compared with ==
    if(e ! =null && e.get() == key)
        // return e
        return e;
    else
        /* If the slot is not valid, call expungeStaleEntry. Return entry if it is found; If not found, return null*/
        return getEntryAfterMiss(key, i, e);
}
Copy the code

2.4.2 setInitialValue method

ThreadLocal method, used to set and return the initial value, is called when the get method does not find the node corresponding to the key!

There are probably the following steps:

  1. Get the return value of the initialValue method as the value of the new node;
  2. Get ThreadLocalMap of current thread, check whether null;
  3. If not null, the current ThreadLocal object is used as the key to store the value, and the method ends.
  4. If null, the ThreadLocalMap of this thread is initialized, and the current ThreadLocal object is used as the key to store the value.
/** * ThreadLocal, set and return the initial value **@returnReturns null or the user-defined initialValue */ through the initialValue method
private T setInitialValue(a) {
    // Returns null or a custom value when the user overrides the method
    T value = initialValue();
    // Get the current thread
    Thread t = Thread.currentThread();
    // Get the map of the current thread
    ThreadLocal.ThreadLocalMap map = getMap(t);
    // If map is not null
    if(map ! =null)
        // Try adding nodes with the current ThreadLocal object as key and null or a custom initial value as value
        map.set(this, value);
    else
        // Otherwise initialize the map and set the value
        createMap(t, value);
    // Return value, null, or a custom initial value
    return value;
}
Copy the code

2.4.2.1 initialValue method

When the get method does not find the data, the setInitialValue method is called. In this method, the initialValue method is called and will return null by default. The user can also override the method to return the specified value, which is equivalent to the default initialValue.

SetInitialValue will hold a node with the current ThreadLocal object as the key and the return value of initialValue as value, and return the value of value.

/** * ThreadLocal returns null ** by default@returnBy default, null is returned. Users can override this method to return a custom initial value */
protected T initialValue(a) {
    return null;
}
Copy the code

2.4.2.2 Default Initial Value cases

public class ThreadLocalInitialValue {
    public static void main(String[] args) {
        Th1 overrides the initialValue method
        ThreadLocal th1 = new ThreadLocal() {
            @Override
            protected Object initialValue(a) {
                return 11; }};//th2 does not override the initialValue method
        ThreadLocal th2 = new ThreadLocal();
        // Neither ThreadLocal's get methods will be able to find the stored data because the set method is not called to set the data
        // Th1 will return the default initial value and set key: th1 value: 11
        System.out.println("Th1 initial value:" + th1.get());
        //th2 will return null and set key: th2 value: null
        System.out.println("Th2 initial value:"+ th2.get()); }}Copy the code

2.5 ThreadLocal memory leaks

2.5.1 Mechanism of Memory Leakage

First, the basics, an introduction to references in Java:Java strong, soft, weak, virtual four object reference in detail and case demonstration.

In the Entry constructor, ThreadLocal is not directly used as the key. Instead, the weak reference object wrapped by ThreadLocal is used as the key. The key is associated with the weak reference field. The key is also obtained from the weak reference field.

Why use weak reference-wrapped ThreadLocal objects as keys? Because if an entry is directly associated with a ThreadLocal object using a common attribute, the key is a strong reference. When the global variable reference of the outermost ThreadLocal object is null, the ThreadLocal object will not be reclaimed because there is a strong reference of the key to the ThreadLocalMap. However, the object can no longer be accessed or used. The key memory leaks.

Therefore, ThreadLocal objects are wrapped as weak references as keys. Thus, when a strong reference to an external ThreadLocal object is removed, the ThreadLocal object is associated only with a weak reference pair, since the weak-reference key is stored in the ThreadLocalMap. The weakly referenced ThreadLocal object can then be cleared automatically during the next GC.

However, there is still a memory leak, but it is a value or Entry memory leak.

We know that value is a strong reference. This leads to a problem. If the weak-reference key is returned to null, the value must still exist if the thread that called ThreadLocal keeps running, and its ThreadLocalMap and internal entry remain. However, it cannot be accessed by the key (because the key has been reclaimed to null), and a memory leak has occurred.

So the safest thing to do is to remove invalid entries.

2.5.2 How to Avoid Memory Leaks

As we can see from the source code of the set and GET methods, when the key of the traversal entry is null, the entry will be cleared and the value will be empty, which can solve the partial memory leak problem. However, this is not absolute. The set and GET methods may return the entry with a null key when the entry is not traversed because the insertion and acquisition are successful. Therefore, in the set and GET methods, only the invalid data traversed will be cleared.

ThreadLocal also has a remove method that removes entries corresponding to this ThreadLocal object. In fact, after the ThreadLocal data is used, the entry is logically invalid. Therefore, the remove method is actively called to remove the entry. In this way, we can manually clear the used entries, fundamentally eliminating the memory leak problem.

Therefore, it is important to develop good programming habits. After using ThreadLocal data, always remember to call the remove method once.

3. Summary and application

Conclusion:

  1. Since each ThreadLocal implements ThreadLocal storage, it can only hold one local data. If you want a thread to hold multiple data, you need to create multiple ThreadLocal.
  2. The key of ThreadLocalMap is a weak reference wrapped by ThreadLocal, and the value is the set value. ThreadLocal runs the risk of leaking memory, so it must be removed manually!

Application:

  1. Typical scenarios for using ThreadLocal, such as database connection management, thread session management, etc., are only applicable to copies of independent variables; if variables are globally shared, they are not suitable for high concurrency.
  2. Spring MVC encapsulates each thread’s Request object in the RequestContextHolder with a ThreadLocal property so that each thread can access its Request.
  3. Spring’s declarative transaction management, each transaction information is encapsulated into the TransactionSynchronizationManager using ThreadLocal attribute, storage and isolation of different threads of transactions, and suspend and resume of the transaction.
  4. You can use the ThreadLocal class to manage transactions in raw JDBC mode!

If you don’t understand or need to communicate, you can leave a message. In addition, I hope to like, collect, pay attention to, I will continue to update a variety of Java learning blog!