ThreadLocal profile

role

ThreadLocal stores its own private data for each thread, preventing its variables from being tampered with by other threads.

Application scenarios

  1. When multiple threads share the same resource and no synchronization is required. There are two ways to solve the problem of multi-thread shared data conflict, one is locking, the other is each thread to create a copy of shared resources, do not interfere with each other. Rather than resolve conflicts when they occur, ThreadLocal avoids them, thus avoiding the performance cost of locking, a space-for-time strategy. For example, multiple threads establish their own database connections to avoid the error caused by competing with the same database connection.
  2. Saves thread context information, which can be retrieved wherever needed inside the thread.

limited

It is not suitable for scenarios where multiple threads need to synchronize and cannot solve the updating problem of shared objects. For example, multiple threads add the same variable, because changes made by one thread need to affect other threads, otherwise the sum will not be correct.

Best practices

Static is recommended for ThreadLocal objects. This variable is shared by all operations within a thread, so it is set to static. All instances of this class share this static variable, which means that the class is loaded when it is first used, only a chunk of storage is allocated, and all objects of this class can manipulate this variable.

If a ThreadLocal is static, the key to an Entry in a ThreadLocalMap will always exist, so remove will be able to locate and delete the Entry. And when an invalid Entry is cleared, the Entry corresponding to this ThreadLocal object will not be cleaned.

When ThreadLocal is not used, the remove method is actively called to clean up the memory to avoid overflow.

public class ThreadLocalTest {
	private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

	public static void main(String[] args) {
		new Thread(() -> {
			try {
				// do something with threadLocal
				threadLocal.set(3);
				threadLocal.get();
			} finally{ threadLocal.remove(); } }).start(); }}Copy the code

Realize the principle of

Each Thread has an internal member attribute of type ThreadLocalMap, threadLocals. ThreadLocalMap is a common class that holds an array of entries. The default length is 16. Each Entry is a store of internal thread data. Entry inherits WeakReference and uses ThreadLocal as the key, that is, it points to ThreadLocal Object through WeakReference. In addition, Entry holds a value of Object type. ThreadLocal represents the type of value and acts as a key to locate the Entry from the array. Value is the internally isolated data of the actual stored thread.

The related classes are Thread, ThreadLocalMap, Entry, ThreadLocal, Object(value).

set

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 ThreadLocal calls set(), it retrieves the current thread first and then its ThreadLocalMap.

ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table =new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
Copy the code

If the map is empty, the map is created. In the constructor of ThreadLocalMap, the Entry array is initialized, the hash method of ThreadLocal calculates the number I in the array, and then the Entry is created and added to the number I position in the array.

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

If the map is not empty, the set() method of the map is called. In the set method, the sequence number I in the array is calculated according to the hash value of ThreadLocal, and the value of the Entry at I is obtained. Then the following four cases are divided:

  1. If the Entry is empty, create an Entry and add it to position I in the array.
  2. If the Entry is not empty and the key of the Entry is the same as the current ThreadLocal object, the value of the Entry is directly replaced.
  3. If the Entry is not empty and the key of the Entry is empty, the Entry is invalid. At this point, the current position I is traversed forward, and the last element of the number group is traversed after the element with the serial number 0 is encountered, until the position where the first Entry is NULL is encountered. For each Entry, replace the value if the key is a ThreadLocal object, and check back for each Entry. If the Entry is not null and the key is null, remove the Entry from the array.
  4. The Entry is not empty, the key of the Entry is not empty, and is not equal to a ThreadLocal object. In this case, the array is iterated backwards until it stops in the preceding three cases.

get

public T get(a) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
                returnresult; }}return setInitialValue();
}
Copy the code

When ThreadLocal calls get, it retrieves the current thread and the map owned by the thread. If the map is not empty, take the Entry at position I in the array based on the hash value of the ThreadLocal object. If the Entry is not empty and its key is equal to the ThreadLocal object, return the value of the Entry. If the Entry is empty or the key is not equal to a ThreadLocal object, the number group is traversed backwards. When the Entry is not empty and the key is not equal to a ThreadLocal object, the number group is traversed backwards. When an Entry with an empty key is encountered, the invalid Entry operation is performed. The default value of ThreadLocal is returned after the cleanup, and the default value of ThreadLocal is returned when Entry is empty.

Memory leaks

Reference relationship

Thread –> Entry –> value; Thread –> Entry –> value

Entry points to a ThreadLocal object by weak reference, and the ThreadLocal object is strongly referenced by the class that created it.

When does a memory leak occur

After using a ThreadLocal, if the remove method is not called, the value of the ThreadLocal object may be held by the Thread until it is destroyed, causing a memory leak. When a thread object is collected by gc, there is no memory leak, but the lifetime of the thread is long, and the common use of thread pools can make the lifetime of the thread even longer. Without manual remove, the value may not be released, which causes a memory leak.

The time before thread destruction can cause a memory leak because it has to do with the definition of the threadLocal object and the mechanism by which ThreadLocalMap cleans up invalid entries. If a threadLocal is defined as a static property of the class, then a strong reference to threadLocal will always exist. In this case, invalid Entry cleaning will not reclaim the Entry corresponding to the theadLocal object, which will definitely cause a memory leak. But if theadLocal is defined as a local variable, or if the class member variable is defined, but the class is destroyed, or if the threadLocal object is pointed to null, then the Entry corresponding to the threadLocal object becomes invalid. It may be cleared by the invalid Entry clearing mechanism, but not always. Memory leaks may still occur. Note that in this case, it is possible, but not certain.

In conclusion, there are two conditions that can cause memory leakage after a ThreadLocal is not used and before the thread is destroyed. First, the thread that created the ThreadLocal holds its strong reference, so the value in the Entry object corresponding to the ThreadLocal will not be collected, resulting in memory leakage. ThreadLocalMap’s get() method happens to be constantly accessing several ThreadLocal entities, and memory cleaning is never performed. Memory leaks occur between expired entries and their corresponding values.

Invalid Entry clearing mechanism

When the set() and get() methods are executed, invalid Entry clearance is performed when an Entry with an empty key is encountered. During clearing, the current position is traversed until the position where the first Entry is NULL ends. Iterate backwards from the new position to clear the Entry with a null key from the array. This cleaning mechanism can be used to programmatically remove entries that do not have strong references to them, but it is not guaranteed to remove all useless threadLocal. One is that if threadLocal always has strong references, their corresponding entries will not be cleared even if they are no longer used. Second, even if a threadLocal does not have a strong reference and the corresponding Entry key is null, if a threadLocal keeps accessing several existing entries, the memory-clear mechanism will not be implemented and memory leaks will occur

How to resolve memory leaks

Because invalid Entry cleaning is not guaranteed to completely remove invalid entries, and because threadLocal objects are statically defined in most implementations, strong references always exist, invalid Entry cleaning does not take place at all. To prevent memory leaks, call ThreadLocal’s remove method as soon as the thread is no longer in use and manually remove it from ThreadLocalMap.

Why does Entry use weak references to hold ThreadLocal

If you use a strong reference to hold a ThreadLocal, then when remove() is not executed, regardless of whether the place where the ThreadLocal was created still holds its reference, the Entry will never be freed because it holds its strong reference, causing a memory leak. With weak references, when the place where a ThreadLocal was created no longer holds its strong reference, the system is left with only the weak reference in the Entry that holds it. When GC occurs, the ThreadLocal is reclaimed, and the Entry key points to NULL. Later, when set(), get(), and so on are executed, it is possible to check for entries with null keys and reclaim them, thus avoiding memory leaks. Another reason is that threads are typically used in thread pools, so threads have a very long life cycle, which may last as long as you deploy them, whereas ThreadLocal objects may not. Using weak references ensures that the Entry of a destroyed ThreadLocal can be cleaned up.

Why does Entry not hold value by weak reference

Because if you hold a value with a weak reference, the value will be cleared during GC, but if the Entry’s corresponding ThreadLocal still exists, the value will be empty.

When do I collect Entry

  1. When you manually call remove
  2. The key referenced by Entry is empty, and when the get(), set(), and remove() methods are executed, the expungeStaleEntry method of ThreadLocalMap is called to remove invalid entries
  3. When the thread is destroyed

Different hash algorithms of ThreadLocalMap and HashMap

Java.util. HashMap uses a linked list method to handle collisions, placing a linked list (red-black tree) at each collision location. ThreadLocalMap uses a simple linear detection method. First, it uses the hash value of ThreadLocal to locate an id I in the current array. If there is an element conflict, it iterates back through the array looking for Entry until it finds the same key or encounters null Entry.

private void set(ThreadLocal
        key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    // Get the index of an array based on the hash value of threadLocal
    int i = key.threadLocalHashCode & (len-1);

    for(Entry e = tab[i]; e ! =null;
        e = tab[i = nextIndex(i, len)]) {
    	// Enter the loop to indicate that the index position is occupied and a conflict has occurredThreadLocal<? > k = e.get();// If the keys are the same, reset them
        if (k == key) {
            e.value = value;
            return;
        }

        // If the key is null, the entry is invalid. Clear the original entry from the list and create a new entry in this position
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return; }}// If there is no entry in the current position, it indicates that no entry is occupied
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

reference

  • I’ve written down everything ThreadLocal can ask