ThreadLocal source code analysis – use of golden section number

premise

A project I recently came into contact with is to be compatible with new and old systems. Finally, ThreadLocal (actually inheritablethreadlocal) is used to obtain variables shared by the parent thread in the child thread. The problem was solved, but it was later found that the understanding of ThreadLocal was not deep enough, so I read and understood its source code by the way. Before talking about ThreadLocal, let's talk about the golden section number. This article uses jdk8 (1.8.0_181) when reading the ThreadLocal source code.

Golden section number and Fibonacci number series

First, review the Fibonacci sequence. The following derivation process comes from the wiki of a search engine:

Interestingly, for such a sequence of completely natural numbers, the general term formula is expressed by irrational numbers. Moreover, when n tends to infinity, the ratio of the former term to the latter term becomes closer and closer to 0.618 (or the fractional part of the ratio of the latter term to the former term becomes closer and closer to 0.618), and this value of 0.618 is called the golden section number. The proof process is as follows:

The exact value of the golden section number is (root 5 - 1) / 2, which is approximately equal to 0.618.

Application of golden section number

Golden section number is widely used in art, photography and other art fields, because it has strict proportionality, artistry and harmony, contains rich aesthetic value, and can stimulate people's beauty. Of course, these are not the research directions of this paper. First, we try to find the specific values of the golden section number of unsigned integers and signed integers:

public static void main(String[] args) throws Exception {
    //黄金分割数 * 2的32次方 = 2654435769 - 这个是无符号32位整数的黄金分割数对应的那个值
	long c = (long) ((1L << 32) * (Math.sqrt(5) - 1) / 2);
	System.out.println(c);
    //强制转换为带符号为的32位整型,值为-1640531527
	int i = (int) c;
	System.out.println(i);
}

Understand through a line segment diagram:

That is, 2654435769 is the golden section value of 32-bit unsigned integer, and - 1640531527 is the golden section value of 32-bit signed integer. The hash magic number in ThreadLocal is 1640531527 (hex 0x61c88647). Why use 0x61c88647 as hash magic number? Let's talk about the rule of hashing the key subscript of ThreadLocal in threadlocalmap (ThreadLocal exists in the form of key in threadlocalmap):

Hash algorithm: keyindex = ((I + 1) * hash_ INCREMENT) & (length - 1)

Where, I is the number of ThreadLocal instances, here is hash_ Increment is the hash magic number 0x61c88647, and length is the number (or capacity) of entries (K-V structure) that can be accommodated in threadlocalmap. The initialization capacity of the internal class threadlocalmap in ThreadLocal is 16. After expansion, it is always to the power of 2. Therefore, we can write a demo to simulate the whole hash process:

public class Main {
	
	private static final int HASH_INCREMENT = 0x61c88647;

	public static void main(String[] args) throws Exception {
		hashCode(4);
		hashCode(16);
		hashCode(32);
	}

	private static void hashCode(int capacity) throws Exception {
		int keyIndex;
		for (int i = 0; i < capacity; i++) {
			keyIndex = ((i + 1) * HASH_INCREMENT) & (capacity - 1);
			System.out.print(keyIndex);
			System.out.print(" ");
		}
		System.out.println();
	}
}

In the above example, we simulated that when the capacity of threadlocalmap is 4,16,32, the capacity expansion is not triggered, and "put" 4,32 elements into the container respectively. The output results are as follows:

3 2 1 0 
7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0 
7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0

After each group of elements is hashed, the whole container is just filled, that is, the perfect hashing is realized. In fact, this is not accidental. In fact, the whole hash algorithm can be transformed into polynomial proof: proof (x - y) * hash_ INCREMENT != 2 ^ n * (n m), in X= y,n != m,HASH_ If the increment is an odd number, the constant is true, and the specific proof can be completed by itself. HASH_ The API document with the value of increment 0x61c88647 is annotated as follows:

What is ThreadLocal

The following API notes refer to ThreadLocal:

ThreadLocal provides thread local variables. These variables are different from normal variables, because each thread has its own independently initialized variable copy when accessing the ThreadLocal instance (through its get or set method). ThreadLocal instance is usually a private static field in the class. Its purpose is to associate the state (for example, user ID or transaction ID) with the thread.

ThreadLocal was written by two master authors in the Java world, Josh Bloch and Doug lea. Josh Bloch is the founder of jdk5 language enhancement, Java collection framework and the author of effective java series. Doug lea is the author of JUC (Java. Util. Concurrent) package and the leader of Java Concurrent Programming. Therefore, the source code of ThreadLocal is worth learning.

Principle of ThreadLocal

ThreadLocal is called thread local (local) variable, but actually it does not store any information. It can be understood as follows: it is a bridge for threads to operate the variables stored in threadlocalmap. It mainly provides initialization, set (), get (), and remove () methods. This may be a bit abstract. The following figure illustrates a simple flow chart of using the set () and get () methods of ThreadLocal instances in threads.

Suppose we have the following code. The thread name of the main thread is main (it may not be main):

public class Main {
	
	private static final ThreadLocal<String> LOCAL = new ThreadLocal<>();
	
	public static void main(String[] args) throws Exception{
		LOCAL.set("doge");
		System.out.println(LOCAL.get());
	}
}

The relationship between thread instance and ThreadLocal instance is as follows:

The above only describes the case of single thread, and because the main thread ignores the step of thread t = new thread(), it will be slightly more complicated if there are multiple threads, but the principle remains the same. The ThreadLocal instance always passes through thread Currentthread() obtains the current operating thread instance, and then operates the member variable of threadlocalmap type in the thread instance. Therefore, it is a bridge and does not have storage function.

ThreadLocal source code analysis

For the source code of ThreadLocal, we need to focus on the methods of set (), get (), and remove ().

Internal properties of ThreadLocal

//获取下一个ThreadLocal实例的哈希魔数
private final int threadLocalHashCode = nextHashCode();

//原子计数器,主要到它被定义为静态
private static AtomicInteger nextHashCode = new AtomicInteger();

//哈希魔数(增长数),也是带符号的32位整型值黄金分割值的取正
private static final int HASH_INCREMENT = 0x61c88647;

//生成下一个哈希魔数
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

It should be noted here that threadlocalhashcode is a final attribute, while the atomic counter variable nexthashcode and the method for generating the next hash magic number nexthashcode () are static variables and static methods. Static variables will only be initialized once. In other words, for each new ThreadLocal instance, its internal threadlocalhashcode will increase by 0x61c88647. for instance:

//t1中的threadLocalHashCode变量为0x61c88647
ThreadLocal t1 = new ThreadLocal();
//t2中的threadLocalHashCode变量为0x61c88647 + 0x61c88647
ThreadLocal t2 = new ThreadLocal();
//t3中的threadLocalHashCode变量为0x61c88647 + 0x61c88647 + 0x61c88647
ThreadLocal t3 = new ThreadLocal();

Threadlocalhashcode is the core variable of the hash algorithm used in the threadlocalmap structure below. For each ThreadLocal instance, its threadlocalhashcode is unique.

Basic structure and source code analysis of internal class threadlocalmap

The ThreadLocal internal class threadlocalmap uses the default modifier, which is accessible by the package (package private). A static class entry is defined inside threadlocalmap. Let's focus on the source code of threadlocalmap. Let's first look at the members and structure:

/**
 * ThreadLocalMap是一个定制的散列映射,仅适用于维护线程本地变量。
 * 它的所有方法都是定义在ThreadLocal类之内。
 * 它是包私有的,所以在Thread类中可以定义ThreadLocalMap作为变量。
 * 为了处理非常大(指的是值)和长时间的用途,哈希表的Key使用了弱引用(WeakReferences)。
 * 引用的队列(弱引用)不再被使用的时候,对应的过期的条目就能通过主动删除移出哈希表。
 */
static class ThreadLocalMap {
 
        //注意这里的Entry的Key为WeakReference<ThreadLocal<?>>
	static class Entry extends WeakReference<ThreadLocal<?>> {
        
		//这个是真正的存放的值
		Object value;
                // Entry的Key就是ThreadLocal实例本身,Value就是输入的值
		Entry(ThreadLocal<?> k,Object v) {
                    super(k);
                    value = v;
                }
	}
        //初始化容量,必须是2的幂次方
	private static final int INITIAL_CAPACITY = 16;

        //哈希(Entry)表,必须时扩容,长度必须为2的幂次方
	private Entry[] table;

        //哈希表中元素(Entry)的个数
	private int size = 0;
 
        //下一次需要扩容的阈值,默认值为0
	private int threshold;
   
        //设置下一次需要扩容的阈值,设置值为输入值len的三分之二
	private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
    
    // 以len为模增加i
    private static int nextIndex(int i,int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }
     
    // 以len为模减少i
    private static int prevIndex(int i,int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }
}

Note a very important point here: threadlocalmap $entry is a weak reference, and the key value is ThreadLocal For the instance itself, the indefinite generic wildcard is used here.

Next, look at the constructor of threadlocalmap:

// 构造ThreadLocal时候使用,对应ThreadLocal的实例方法void createMap(Thread t,T firstValue)
ThreadLocalMap(ThreadLocal<?> firstKey,Object firstValue) {
    // 哈希表默认容量为16
    table = new Entry[INITIAL_CAPACITY];
    // 计算第一个元素的哈希码
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey,firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

// 构造InheritableThreadLocal时候使用,基于父线程的ThreadLocalMap里面的内容进行提取放入新的ThreadLocalMap的哈希表中
// 对应ThreadLocal的静态方法static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap)
private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];
    // 基于父ThreadLocalMap的哈希表进行拷贝
    for (Entry e : parentTable) {
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                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++;
            }
        }
    }
}

Note here that when the set () method of ThreadLocal is called, it will lazy initialize a threadlocalmap and put the first element. The private construction of threadlocalmap is provided to the static method threadlocal#createinheritedmap().

Next, look at some instance methods provided by threadlocalmap for ThreadLocal:

// 如果Key在哈希表中找不到哈希槽的时候会调用此方法
private Entry getEntryAfterMiss(ThreadLocal<?> key,int i,Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    // 这里会通过nextIndex尝试遍历整个哈希表,如果找到匹配的Key则返回Entry
    // 如果哈希表中存在Key == null的情况,调用expungeStaleEntry进行清理
    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i,len);
        e = tab[i];
    }
    return null;
}

// 1.清空staleSlot对应哈希槽的Key和Value
// 2.对staleSlot到下一个空的哈希槽之间的所有可能冲突的哈希表部分槽进行重哈希,置空Key为null的槽
// 3.注意返回值是staleSlot之后的下一个空的哈希槽的哈希码
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    
    // expunge entry at staleSlot
    // 清空staleSlot对应哈希槽的Key和Value
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    // 下面的过程是对staleSlot到下一个空的哈希槽之间的所有可能冲突的哈希表部分槽进行重哈希,置空Key为null的槽
    Entry e;
    int i;
    for (i = nextIndex(staleSlot,len); (e = tab[i]) != null; i = nextIndex(i,len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;

                // Unlike Knuth 6.4 Algorithm R,we must scan until
                // null because multiple entries Could have been stale.
                while (tab[h] != null)
                    h = nextIndex(h,len);
                tab[h] = e;
            }
        }
    }
    return i;
}

// 这里个方法比较长,作用是替换哈希码为staleSlot的哈希槽中Entry的值
private void replaceStaleEntry(ThreadLocal<?> key,Object value,int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    Entry e;

    // Back up to check for prior stale entry in current run.
    // We clean out whole runs at a time to avoid continual
    // incremental rehashing due to garbage collector freeing
    // up refs in bunches (i.e.,whenever the collector runs).
    int slotToExpunge = staleSlot;
    // 这个循环主要是为了找到staleSlot之前的最前面的一个Key为null的哈希槽的哈希码
    for (int i = prevIndex(staleSlot,len); (e = tab[i]) != null; i = prevIndex(i,len))
        if (e.get() == null)
            slotToExpunge = i;

    // Find either the key or trailing null slot of run,whichever
    // occurs first
    // 遍历staleSlot之后的哈希槽,如果Key匹配则用输入值替换
    for (int i = nextIndex(staleSlot,len)) {
        ThreadLocal<?> k = e.get();

        // If we find key,then we need to swap it
        // with the stale entry to maintain hash table order.
        // The newly stale slot,or any other stale slot
        // encountered above it,can then be sent to expungeStaleEntry
        // to remove or rehash all of the other entries in run.
        if (k == key) {
            e.value = value;
            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;
            // Start expunge at preceding stale entry if it exists
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);
            return;
        }

        // If we didn't find stale entry on backward scan,the
        // first stale entry seen while scanning for key is the
        // first still present in the run.
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }
    // Key匹配不了,则新创建一个哈希槽
    // If key not found,put new entry in stale slot
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key,value);
    
    // 这里如果当前的staleSlot和找到前置的slotToExpunge不一致会进行一次清理
    // If there are any other stale entries in run,expunge them
    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);
}

// 对当前哈希表中所有的Key为null的Entry调用expungeStaleEntry
private void expungeStaleEntries() {
    Entry[] tab = table;
    int len = tab.length;
    for (int j = 0; j < len; j++) {
        Entry e = tab[j];
        if (e != null && e.get() == null)
            expungeStaleEntry(j);
    }
}

// 清理第i个哈希槽之后的n个哈希槽,如果遍历的时候发现Entry的Key为null,则n会重置为哈希表的长度,expungeStaleEntry有可能会重哈希使得哈希表长度发生变化
private boolean cleanSomeSlots(int i,int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i,len);
        Entry e = tab[i];
        if (e != null && e.get() == null) {
            n = len;
            removed = true;
            i = expungeStaleEntry(i);
        }
    } while ( (n >>>= 1) != 0);
    return removed;
}


/**
 * 这个方法主要给`ThreadLocal#get()`调用,通过当前ThreadLocal实例获取哈希表中对应的Entry
 *
 */
private Entry getEntry(ThreadLocal<?> key) {
    // 计算Entry的哈希值
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i]; 
    if (e != null && e.get() == key)
        return e;
    else  // 注意这里,如果e为null或者Key对不上,会调用getEntryAfterMiss
        return getEntryAfterMiss(key,i,e);
}

// 重哈希,必要时进行扩容
private void rehash() {
    // 清理所有空的哈希槽,并且进行重哈希
    expungeStaleEntries();

    // Use lower threshold for doubling to avoid hysteresis
    // 哈希表的哈希元素个数大于3/4阈值时候触发扩容
    if (size >= threshold - threshold / 4)
        resize();
}

// 扩容,简单的扩大2倍的容量        
private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;

    for (Entry e : oldTab) {
        if (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                e.value = null; // Help the GC
            } else {
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null)
                     h = nextIndex(h,newLen);
                newTab[h] = e;
                count++;
            }
        }
    }

    setThreshold(newLen);
    size = count;
    table = newTab;
}

// 基于ThreadLocal作为key,对当前的哈希表设置值,此方法由`ThreadLocal#set()`调用
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);
    // 变量哈希表
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i,len)]) {
        ThreadLocal<?> k = e.get();
        // Key匹配,直接设置值
        if (k == key) {
            e.value = value;
            return;
        }
        // 如果Entry的Key为null,则替换该Key为当前的key,并且设置值
        if (k == null) {
            replaceStaleEntry(key,value,i);
            return;
        }
    }

    tab[i] = new Entry(key,value);
    int sz = ++size;
    // 清理当前新设置元素的哈希槽下标到sz段的哈希槽,如果清理成功并且sz大于阈值则触发扩容
    if (!cleanSomeSlots(i,sz) && sz >= threshold)
        rehash();
}

In short, threadlocalmap is the real data storage container of ThreadLocal. In fact, all logic of the complex part of ThreadLocal data operation is carried out in threadlocalmap, and threadlocalmap instance is a member variable of thread, which is set to the current thread instance when the threadlocal#set () method is called for the first time. If multiple ThreadLocal instances are used in the same thread, in fact, each ThreadLocal instance corresponds to a hash slot in the hash table of threadlocalmap. For example, multiple ThreadLocal instances are used in the main thread of the main function:

public class ThreadLocalMain {

	private static final ThreadLocal<Integer> TL_1 = new ThreadLocal<>();
	private static final ThreadLocal<String> TL_2 = new ThreadLocal<>();
	private static final ThreadLocal<Long> TL_3 = new ThreadLocal<>();

	public static void main(String[] args) throws Exception {
		TL_1.set(1);
		TL_2.set("1");
		TL_3.set(1L);
		Field field = Thread.class.getDeclaredField("threadLocals");
		field.setAccessible(true);
		Object o = field.get(Thread.currentThread());
		System.out.println(o);
	}
}

In fact, the hash table in the threadlocals attribute of the main thread generally contains more than the three threadlocales defined above, because ThreadLocal may be used in other places when loading the main thread. The results of a debug by the author are as follows:

@H_ 320_ 301@

Drawing with PPT is simplified as follows:

The table in the row of threadlocalhashcode attribute in the above figure is to mark the hash value of the hash slot of each entry. In fact, threadlocalhashcode is ThreadLocal@XXXX It is obvious that threadlocalhashcode is a member variable of ThreadLocal.

The above is only a simple and rough analysis of the source code of threadlocalmap. The following will make some detailed diagrams to illustrate the process of some core operations in ThreadLocal and threadlocalmap.

Creation of ThreadLocal

From the constructor of ThreadLocal, the construction of ThreadLocal instance does not do any operation, but just to get a generic instance of ThreadLocal, which can be used as the key of threadlocalmap $entry later:

// 注意threadLocalHashCode在每个新`ThreadLocal`实例的构造同时已经确定了
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

// 通过supplier去覆盖initialValue方法
public static <S> ThreadLocal<S> withInitial(supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);
}

// 默认公有构造函数
public ThreadLocal() {

}

Note that threadlocalhashcode has been determined in the construction of each new ThreadLocal instance, and this value is also the hash value bound by the hash slot of the entry hash table.

Set method of treadlocal

The source code of the set() method in ThreadLocal is as follows:

public void set(T value) {
	//设置值前总是获取当前线程实例
    Thread t = Thread.currentThread();
	//从当前线程实例中获取threadLocals属性
    ThreadLocalMap map = getMap(t);
    if (map != null)
	     //threadLocals属性不为null则覆盖key为当前的ThreadLocal实例,值为value
         map.set(this,value);
    else
	//threadLocals属性为null,则创建ThreadLocalMap,第一个项的Key为当前的ThreadLocal实例,值为value
        createMap(t,value);
}

// 这里看到获取ThreadLocalMap实例时候总是从线程实例的成员变量获取
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// 创建ThreadLocalMap实例的时候,会把新实例赋值到线程实例的threadLocals成员
void createMap(Thread t,T firstValue) {
     t.threadLocals = new ThreadLocalMap(this,firstValue);
}

The above process source code is very simple. When setting the value, always get the current thread instance first and operate its variable threadlocales. The steps are:

Get method of treadlocal

The source code of get() method in ThreadLocal is as follows:

 public T get() {
   //获取当前线程的实例
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    //根据当前的ThreadLocal实例获取ThreadLocalMap中的Entry,使用的是ThreadLocalMap的getEntry方法
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T) e.value;
             return result;
            }
        }
    //线程实例中的threadLocals为null,则调用initialValue方法,并且创建ThreadLocalMap赋值到threadLocals
    return setInitialValue();
}

private T setInitialValue() {
    // 调用initialValue方法获取值
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // ThreadLocalMap如果未初始化则进行一次创建,已初始化则直接设置值
    if (map != null)
        map.set(this,value);
    else
        createMap(t,value);
    return value;
}

protected T initialValue() {
     return null;
}

The initialvalue() method returns null by default. If the ThreadLocal instance has not used the set() method and directly uses the get() method, the item with ThreadLocal as key in threadlocalmap will set the value as the return value of the initialvalue() method. If you want to change this logic, you can override the initialvalue () method.

Remove method of treadlocal

The source code of the remove() method in ThreadLocal is as follows:

public void remove() {
	//获取Thread实例中的ThreadLocalMap
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
	   //根据当前ThreadLocal作为Key对ThreadLocalMap的元素进行移除
       m.remove(this);
}

ThreadLocal. Initialization of threadlocalmap

We can focus on Java Variables in lang.Thread class:

public class Thread implements Runnable {

  //传递ThreadLocal中的ThreadLocalMap变量
  ThreadLocal.ThreadLocalMap threadLocals = null;
  //传递InheritableThreadLocal中的ThreadLocalMap变量
  ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

That is, the data that ThreadLocal needs to store and obtain is actually bound to the member variable threadlocals of the thread instance, and is loaded lazily only when the threadlocal#set () method is called. You can understand it in combination with the contents of the previous section. It is not expanded here.

Under what circumstances will the use of ThreadLocal lead to memory leakage

In fact, ThreadLocal itself does not store any data, while the data in ThreadLocal is actually stored in the thread instance. In fact, it is a thread memory leak. At the bottom, it is the member variable threadlocals in the thread object that holds a large number of K-V structures, and the thread is always active, resulting in the variable threadlocals being unable to be released and recycled. Threadlocales hold a large number of K-V structures. The premise of this is that there are a large number of ThreadLocal instance definitions. Generally speaking, it is impossible for an application to define a large number of threadlocales. Therefore, the general leakage source is that the thread is always active, so the variable threadlocales cannot be released and recycled. However, we know that weak references (· WeakReference < ThreadLocal >) are used for the keys of the entry structure in the threadlocalmap. When there is no strong reference to reference the ThreadLocal instance, the GC of the JVM will recycle these keys in the threadlocalmap. At this time, some entry items with null key but non null value will appear in the threadlocalmap. If these entry items are not actively cleaned up, Will always reside in threadlocalmap. That is why the get(), set(), and remove() methods in ThreadLocal contain code blocks that clean up threadlocalmap instance keys that are null. To sum up, the possible memory leaks are:

A design highlight of ThreadLocal is that the key of the entry structure in threadlocalmap uses a weak reference. Imagine that if strong references are used, it means that all data in threadlocalmap is bound to the life cycle of the thread, which is prone to memory leakage caused by the continuous activity of a large number of threads. If weak references are used, after the JVM triggers GC to reclaim weak references, ThreadLocal can delete those values with null key in threadlocalmap by calling get(), set(), and remove() methods next time, which plays the role of inert deletion to free memory.

In fact, ThreadLocal sets the internal class ThreadLocal The entry hash table built in threadlocalmap has considered the problem of memory leakage, so ThreadLocal The threadlocalmap $entry class is designed as a weak reference, and the class signature is static class entry extensions WeakReference < ThreadLocal >. As mentioned in the previous article, if the object associated with a weak reference is set to null, the weak reference will reclaim the object associated with a weak reference at the next GC. for instance:

public class ThreadLocalMain {

	private static ThreadLocal<Integer> TL_1 = new ThreadLocal<>();

	public static void main(String[] args) throws Exception {
		TL_1.set(1);
		TL_1 = null;
		System.gc();
		Thread.sleep(300);
	}
}

In this case, TL_ 1 this ThreadLocal is the ThreadLocal bound by the thread after the active GC The original TL in the entry hash table in the threadlocalmap instance_ The reference holding value referent (inherited from WeakReference) of the hash slot entry where 1 is located will become null, but the value in the entry is a strong reference and also stores TL_ 1 this ThreadLocal does not reclaim the previous value. These "isolated" hash slot entries are the previously mentioned hash slots to be deleted lazily.

ThreadLocal best practices

In fact, ThreadLocal's best practice is simple:

The best time to call the remove () method is called in the finally code block before the thread runs, so that the memory leak caused by improper operation can be avoided completely. This active cleaning way is more effective than inert deletion.

Parent child thread data transfer inheritablethreadlocal

It will be left for the next article, because inheritablethreadlocal can only pass variables through parent-child (1 - > 1) processes. Threads in the thread pool may be shared by multiple parent threads (that is, tasks submitted by one parent thread may be executed by multiple child threads in the thread pool). Therefore, there may be problems. In order to solve this problem, Ali has written a framework - transmittable thread local, which solves the variable transfer problem between the parent thread and the thread in the thread pool.

Summary

ThreadLocal thread local variable is the bridge between thread instance passing and storing shared variables. The real shared variables are still stored in the properties of thread instance itself. The basic logic in ThreadLocal is not complex, but once it involves performance impact, memory recycling (weak reference) and lazy deletion, in fact, its considerations are relatively comprehensive and effective.

Personal blog

(at the end of e-a-20190217, c-7-d of this article, it is found that the article has been stolen and washed, and the watermark of the picture has also been removed. The watermark of the next picture should be in the middle.)

The official account of Technology (Throwable Digest), which is not regularly pushed to the original technical article (never copied or copied):

Entertainment official account ("sand sculpture"), select interesting sand sculptures, videos and videos, push them to relieve life and work stress.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>