Java concurrency: ThreadLocal
Preface
Looking at multithreading recently, I saw the ThreadLocal class and checked it with baidu. The most common description of this class isThreadLocal provides a new approach to solve the concurrent problem of multi-threaded programs. Using this tool class, you can easily compile beautiful multi-threaded programs.Please forget these instructions, because they are totally wrong !!! After reading these blogs, you will be more confused, because their descriptions of ThreadLocal cannot be used to solve multi-thread concurrency. This makes it hard to figure out what ThreadLocal is.
Let's take a look at the descriptions of ThreadLocal in these blogs:When ThreadLocal is used to maintain a variable, ThreadLocal provides an independent copy of the variable for each thread that uses the variable. Therefore, each thread can change its own copy independently, it does not affect the copies corresponding to other threads.
If ThreadLocal is not displayed, it will provideIndependentCopy !!! That is to say, the operation between each thread does not affect other threads. It is equivalent to setting up a local variable for the internal declaration cycle of a thread, and it does not matter if it does not affect other threads. Therefore, their logic is chaotic, and writing such information is wrong.
After reading this, I have never understood the specific role of ThreadLocal. I didn't understand it until I saw a blog in winwill2012. This blog also references this article. The article address is here [Java development package learning 7] decrypting ThreadLocal
The role of ThreadLocal in this blog squadron is as follows:ThreadLocal is used to provide local variables in the thread. This variable takes effect within the thread's life cycle, reducing the complexity of passing some public variables between multiple functions or components in the same thread.
ThreadLocal Method Introduction
ThreadLocal has only four basic methods:void set(T value)
,T get()
,void remove()
AndT initialValue()
. The descriptions are as follows:
InitialValue () method
This method is used to return the initial value of the current thread in ThreadLocal.get()
It will be called for the first time, but if it is called at the beginningset()
, The function will not be called. Generally, this function is called only once unless it is manually called.remove()
And then callget()
In this caseget()
Will still be calledinitialValue()
. The specific implementation is as follows:
protected T initialValue() { return null;}
This method isprotected
It is obviously recommended that this method be reloaded in the subclass. Therefore, this method is usually overloaded in the form of an anonymous internal class to specify the initial value. For example:
private static final ThreadLocal
value = new ThreadLocal
() { @Override protected Integer initialValue() { return Integer.valueOf(1); } };
Get () method
This method gets the ThreadLocal value associated with the current thread. The method declaration is as follows:
public T get()
Set () method
This method is used to set the ThreadLocal value associated with the current thread. The method declaration is as follows:
public void set(T value)
Remove () method
This method is used to delete the ThreadLocal bound value of the current thread. The method declaration is as follows:
public void remove()
Example
Use a piece of code to demonstrate ThreadLocal usage
Public class Test {private static final ThreadLocal
Value = new ThreadLocal
() {@ Override protected Integer initialValue () {return 0 ;}}; public static void main (String [] args) {for (int I = 1; I <= 5; I ++) {new Thread (new LocalThread (I )). start () ;}} static class LocalThread implements Runnable {private int index; public LocalThread (int index) {this. index = index;} public void run () {System. out. println ("Thread" + index + "Initial value:" + value. get (); for (int I = 0; I <10; I ++) {value. set (value. get () + I);} System. out. println ("Thread" + index + "cumulative value:" + value. get ());}}}
Running result:
Initial value of thread 1: Initial value of Thread 0 5: accumulated value of Thread 0 1: Initial value of thread 45 2: accumulated value of Thread 0 2: initial value of 45 thread 3: initial value of 0 thread 4: accumulated value of 0 thread 4: accumulated value of 45 thread 3: accumulated value of 45 thread 5: 45
We can see that the values in each thread are independent, and the accumulative operation of this thread does not affect the values of other threads, which truly achieves internal isolation of threads.
Source code parsing
Source code is taken from JDK 8. Let's take a look at it first.get()
Source code:
public T get() { 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; return result; } } return setInitialValue(); }
We can see thatget()
When calling a methodgetMap()
Method to obtainThreadLocalMap
Object. Let's look at it first.getMap()
Source code:
ThreadLocalMap getMap(Thread t) { return t.threadLocals;}
From the source code, we can see thatthreadLocals
Member object,
ThreadLocal.ThreadLocalMap threadLocals = null;
Then let's look at the setInitailValue () method in the ThreadLocal get () method.
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;}
Source code of the createMap () method:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue);}
Now we probably know the process of getting the get method:
1. First, Thread. currentThread () gets the current Thread
2. Obtain a Map in the thread based on the current thread. If the map is empty, convert it to 4.
3. if this map is not empty, the value e corresponding to the current ThreadLocal reference is obtained in the map. If e is not empty, the return value e. value. If it is null, it is converted to 4.
4. If Map is empty or e is empty, the initialValue function is used to obtain the initial value, and ThreadLocal reference and value are used as the firstKey and firstValue to create a new Map.
Therefore, we can summarize the ThreadLocal design ideas:
Each Thread maintains a ThreadLocalMap ing table. The key of this ing table is the ThreadLocal instance itself, and the value is the Object to be stored.
Why is it so difficult to design, instead of maintaining a Map directly in ThreadLocal, and then using the thread ID as the Map key. After reading the materials, the design has the following advantages:
* After this design, the number of entries for each Map becomes smaller: the number of threads is used before, and the number of ThreadLocal is used now, which can improve the performance. It is said that the performance is not improved by two points (no tests are conducted)
* After the Thread is destroyed, the corresponding ThreadLocalMap is destroyed, which can reduce the memory usage.
Memory leakage
The usage of ThreadLocal on the Internet may cause memory leakage. What is the specific cause? Is that true?
First, ThreadLocal stores all data.ThreadLocalMap
In a type object, this class is an internal class of ThreadLocal. Let's take a look at it first.ThreadLocalMap
Source code:
static class ThreadLocalMap { static class Entry extends WeakReference
> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal
k, Object v) { super(k); value = v; } } ... ...}
We can see thatThreadLocalMap uses the weak reference of ThreadLocal as the Key.
The above is the reference relationship between some objects described in this article. The solid line indicates strong reference, and the dotted line indicates weak reference.
On the Internet, it is rumored that ThreadLocal will cause memory leakage for the following reasons:
For example, ThreadLocalMap uses the weak reference of ThreadLocal as the key. If a ThreadLocal does not have a strong external reference to reference it, The ThreadLocal will be recycled during system gc, in ThreadLocalMap, the Entry with the key being null will appear, and there is no way to access the values of these entries with the key being null. If the current thread is delayed, the value of the Entry whose key is null will always have a strong reference chain:
Thread Ref-> Thread-> ThreaLocalMap-> Entry-> value
It will never be recycled, causing memory leakage. <喎?http: www.bkjia.com kf ware vc " target="_blank" class="keylink"> Ghost/vMLHtb3V4tbWx + m/9sHLoaPH67 + ghost = "brush: java;"> private Entry getEntry(ThreadLocal key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);}
getEntryAfterMiss()
Source code:
private Entry getEntryAfterMiss(ThreadLocal
key, int i, Entry e) { Entry[] tab = table; int len = tab.length; 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; }
expungeStaleEntry()
Source code:
private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter 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; }
Sort out the getEntry function process of ThreadLocalMap:
1. First obtain Entry e from the direct index location of ThreadLocal (obtained through ThreadLocal. threadLocalHashCode & (len-1) operation), if e is not null and the key is the same, return e;
2. if e is null or the key is inconsistent, It is queried to the next location. If the key in the next location is the same as the key to be queried, the corresponding Entry is returned. Otherwise, if the key value is null, the Entry at the location is erased, and the container is adjusted again. Otherwise, the query continues to the next location.
In this process, any Entry with null key will be erased, so the value in the Entry will not have a strong reference chain, and will naturally be recycled. After carefully studying the code, we can find that,set
The Operation also has a similar idea: delete all the entries whose key is null to prevent memory leakage.
However, this is not enough. The above design idea depends on one precondition:Call the getEntry function or set function of ThreadLocalMap.. This is certainly not true in any situation, so in many cases you need to manually call ThreadLocalremove
Function to manually delete ThreadLocal that is no longer needed to prevent memory leakage. Therefore, JDK recommends defining the ThreadLocal variableprivate static
In this case, the life cycle of ThreadLocal is longer. Because of the strong reference of ThreadLocal, ThreadLocal will not be recycled, this ensures that the value of the Entry can be accessed at any time based on the weak reference of ThreadLocal, and then removed to prevent memory leakage.