ThreadLocal source code and analysis of memory leaks

Article directory

1. Introduction to ThreadLocal

When multiple threads access the same shared variable, concurrency problems are prone to occur, especially when multiple threads write to a variable. In order to ensure thread safety, general users need to perform additional synchronization measures when accessing shared variables. thread safety. ThreadLocal is a way to ensure a way to avoid thread insecurity in multi-threaded access in addition to the synchronization method of locking. When we create a variable, if each thread accesses it, the access is The thread’s own variable so that there is no thread unsafety problem.

ThreadLocal is provided by the JDK package. It provides thread local variables. If you create a ThreadLocal variable, each thread that accesses this variable will have a copy of the variable. In the actual multi-threaded operation, it operates its own local memory. variables in the , thus avoiding thread safety issues, as shown in the following figure

2. Simple use of ThreadLocal

package test;

public class ThreadLocalTest {

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

    static  void  print ( String str ) {
         //Print the value of the local variable in the local memory of the current thread 
        System. out .println(str + " :" + localVar. get ());
         //Clear the local variable 
        localVar in the local memory .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 the print method 
                print( " thread1" ) ;
                 //Print the local variable 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 the print method 
                print( " thread2" ) ;
                 //Print the local variable System.out 
                .println ( "after remove : " + localVar.get ( ));
            }
        });

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

3. Internal design of ThreadLocal

3.1 Early Design

3.2 The current design

3.3 The benefits of optimization

1: The number of entries in the map is reduced: because the previous map entry is determined by how many Threads there are, and after JDK1.8, the key of the map entry is ThreadLocal as the key. From the previous code, we can know, We declared 2 threads above, but only used 1 ThreadLocal. The number of ThreadLocals in the actual code is less than
2 of the number of threads: when the Thread is destroyed, the ThreadLocal will also be destroyed: because Thread is used as ThreadLocal As a whole, multiple ThreadLocals are composed of TheadlocalMap as a variable of Thread. If there are no objects, the variables of the objects will also disappear.

4. The core source code of ThreadLocal

4.1 set method

public  void  set ( T value ) {
     //(1) Get the current thread (caller thread)
    Thread t = Thread.currentThread();
    //(2) According to the current thread, get the ThreadLocalMap corresponding to the current county
    ThreadLocalMap map = getMap(t);
    if (map != null )
     //The current ThreadLocal that the map is not null is the key, and the variable is the value to assign 
        map. set ( this , value );
     //(4) If the map is null, it means that it is added for the first time and needs to be First create the corresponding map, which is a new map, and then perform the above set operation 
    else 
        createMap(t, value );
}

//getMap 
ThreadLocalMap getMap ( Thread t ) {
         /**Get the ThreadLocalMap of the current thread**/ 
        return t.threadLocals;
    }

 //Explanation of threadLocals attribute 
 ThreadLocal.ThreadLocalMap threadLocals = null ;

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

Summarize:

4.2 get method

public T get() {
          //1: Get the current thread
        Thread t = Thread.currentThread();
        //2: Get the map maintained by the thread
        ThreadLocalMap map = getMap(t);
        if (map != null ) {
            //3: If it exists, use the current object as the key to get the Entry corresponding to 
            ThreadLocal ThreadLocalMap.Entry e = map.getEntry( this );
             //4: If the entry is not empty, get corresponding value

            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }


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

//5:
  private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

4.3 Remove method

public void remove() {
          //Get the ThreadLocal maintained by the current thread
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null )
          //If it exists, remove the entry corresponding to the corresponding key 
             m. remove ( this );
     }

4.4 setInitialValue method

protected T initialValue() {
        return null;
    }

Default is null. But we can override this method to assign the initial value

4. ThreadLocalMap source code analysis

4.1 Overall structure

4.2 Member variables

4.3 Storage Structure

5: Weak references and memory leaks

For more details, please refer to this big guy’s blog
/javaee6/p/4763190.html

Note: If a weak reference is referenced by a strong reference, it still retains the properties of a weak reference

5.1 Will there be a memory leak if I use strong references?

1: The above ThreadLocalRef and CurrentThreadRef are the references of ThreadLocal and CurrentThread respectively, and they are stored in the stack respectively.

5.2 Will there be a memory leak if we use weak references?

5.3 The real cause of memory leaks

5.4 Why did JDK choose weak references in the end?

6. ThreadLocalMap has no linked list in Map, how to solve hash conflict?

6.1 Constructor

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
             // calculation index 
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1 );
            table[i] = new Entry(firstKey, firstValue);
             //The number of entries stored, because it is a constructor, so it is 1 
            size = 1 ;
            setThreshold(INITIAL_CAPACITY);
        }


============================threadLocalHashCode related code====================== ===================
 private final int threadLocalHashCode = nextHashCode();

 private  static  int  nextHashCode ()  {
          //HASH_INCREMENT is to make the hash evenly distributed, try to avoid 
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

 public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

   public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this .getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
 ==========================INITIAL_CAPACITY - 1)============================================
 Anyone who has studied HashMap knows that the capacity is set to the nth power of 2. When performing -1 , the last digit of the binary system is 1 at this time, which is equivalent to improving the efficiency. For example, 1 and 0 for 4 budget, if it is 1 at this time, you basically don’t need to look at it later.

6.2 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;
             //Calculate the index, just analyzed it 
            int i = key.threadLocalHashCode & (len -1 );
             //Use the linear search method to find the element 
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e. get ();
                  //The key corresponding to threadlocal exists, overwriting the previous value 
                if (k == key) {
                    e.value = value;
                    return;
                }
                //If it does not exist, it means that it has been recycled. The entry in the current data is an obsolete element 
                if (k == null ) {
                    //Replace the old element with a new element. This method performs a lot of garbage cleaning actions to prevent memory leak 
                    replaceStaleEntry(key, value , i);
                     return ;
                }
            }
           //If the key does not exist and no stale element is found, create a new entry at an empty position 
            tab[i] = new Entry(key, value );
             int sz = ++size;
             //clear e. get() = null element 
            //The object associated with the data associated key has been recycled, so entry (table (index) can be set to null 
            //If no entry is clear, and the current usage has reached Load money, then rehash 
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

Summarize:

The biggest difference from HashMap is that the structure of ThreadLocalMap is very simple and there is no next reference. That is to say, the way to solve Hash conflicts in ThreadLocalMap is not the way of linked list, but the way of linear detection. The so-called linear detection is based on the hashcode of the initial key. The value determines the position of the element in the table array. If it is found that an element with other key values ​​is already occupied at this position, a fixed algorithm is used to find the next position of a certain step size, and judge in turn until a position that can be stored is found.

The way ThreadLocalMap resolves Hash conflicts is to simply increase or decrease the step size by 1 to find the next adjacent location.

/**
 * Increment i modulo len.
 */
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

/**
 * Decrement i modulo len.
 */
private static int prevIndex(int i, int len) {
    return ((i - 1 >= 0) ? i - 1 : len - 1);
}

Obviously, the efficiency of ThreadLocalMap to solve Hash conflicts by linear detection is very low. If there are a large number of different ThreadLocal objects put into the map to send conflicts, or a secondary conflict occurs, the efficiency is very low.

So the good suggestion here is: only one variable is stored in each thread, so that the keys stored in the map by all threads are the same ThreadLocal. If a thread wants to save multiple variables, it needs to create multiple ThreadLocals. When a ThreadLocal is put into the Map, it will greatly increase the possibility of Hash conflict

Leave a Comment

Your email address will not be published. Required fields are marked *