ThreadLocal source code analysis_ 01 introduction case and surface source code analysis
1. Introduction to ThreadLocal
-
ThreadLocal is a global object, and ThreadLocal is a solution for variable sharing within a thread** ThreadLocal can be regarded as a set of map s. key is the current thread and value is the variable to be stored** eg as follows:
//Declare a ThreadLocal instance ThreadLocal threadLocal = new ThreadLocal(); //Randomly obtain a number h int n = new Random().nextInt(); //Store data h to ThreadLocal threadLocal.set(data); //Fetch data from threadLocal threadLocal.get();
-
The referenced variables of ThreadLocal will be automatically destroyed after they are used up, regardless of the space occupied by the variables in ThreadLocal.
-
There is a Map in the ThreadLocal class to store the variable copy of each thread. The key of the element in Mpa is the thread object and the value is the corresponding thread variable copy.
-
ThreadLocal functions:
- ThreadLocal solves the conflict of variable concurrent access by providing an independent variable copy for each thread
- Pass data: you can pass public variables in different components of the same thread through ThreadLocal
- Thread isolation: the variables of each thread are independent and will not affect each other.
/** * @author wcc * @date 2021/8/25 19:08 * ThreadLocal By providing an independent variable copy for each thread, the conflict of variable concurrent access is solved */ public class ThreadTest { //Atomic integer, a Thread ID assigned to a thread private static final AtomicInteger nextId=new AtomicInteger(0); /** * Each thread corresponds to a Thread ID */ private static final ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>(){ /** * Initializes the value of a nextId * @return */ @Override protected Integer initialValue() { //nextId auto increment return nextId.getAndIncrement(); } }; /** * Returns the ThreadID corresponding to the current thread. It will be allocated if necessary * @return */ public static int get(){ return threadLocal.get(); } static class RunableTask implements Runnable{ @Override public void run() { System.out.println("The current thread name is:"+Thread.currentThread().getName()+ ",Allocated ID Is:"+threadLocal.get()); //Note that the initialValue() method is called internally by default when threadLocal.get() is called threadLocal.remove(); } } public static void main(String[] args) throws InterruptedException { RunableTask task = new RunableTask(); Thread t1 = new Thread(task,"Thread 1"); t1.start(); TimeUnit.MILLISECONDS.sleep(100); Thread t2 = new Thread(task,"Thread 2"); t2.start(); TimeUnit.MILLISECONDS.sleep(100); Thread t3 = new Thread(task,"Thread 3"); t3.start(); TimeUnit.MILLISECONDS.sleep(100); Thread t4 = new Thread(task,"Thread 4"); t4.start(); TimeUnit.MILLISECONDS.sleep(100); Thread t5 = new Thread(task,"Thread 5"); t5.start(); TimeUnit.MILLISECONDS.sleep(100); } }
The operation results are as follows:
Current thread Name: thread 1, allocated ID Is: 0 Current thread Name: thread 2, allocated ID Is: 1 Current thread Name: thread 3, allocated ID Is: 2 Current thread Name: thread 4, allocated ID Is: 3 Current thread Name: thread 5, allocated ID Is: 4
From the above case, we can see that ThreadLocal variables (nextId) of each thread are independent and will not affect each other.
3. Source code analysis
1, Member properties
/** * threadLocalHashCode ---> Bucket addressing for threadLocals: * 1.When the thread obtains threadLocal.get(): * If this is the first time to get on a threadLocal object, a value will be assigned to the current object * The value and the current threadLocal object are wrapped into an entry object, where the key of the entry is the threadLocal object * value Is the value generated by threadLocal for the current thread * 2.Which bucket of the threadlocales map of the current thread is this entry stored in? * Bucket addressing is related to the threadLocalHashCode of the current threadLocal object * Use the current threadLocal object threadlocalhashcode & (table.length-1) * The location of the calculation result is the location where the current entry needs to be stored in the Entry[] table */ private final int threadLocalHashCode = nextHashCode(); /** * nextHashCode:Indicates the hash value * This property will be used when creating ThreadLocal object * Each time a threadLocal object is created, a hash value is assigned to the object using nextHashCode */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * HASH_INCREMENT,: Represents the increment of the hash value * For each ThreadLocal object created, ThreadLocal.HASH_INCREMENT(0x61c88647) * This value is very special. It is Fibonacci number, also known as Huang Jian's partition number * hash When the increment is this number, the benefit is that the hash distribution is very uniform */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns a hash value of nextHashCode: * When creating a new ThreadLocal object, this method will assign a hash value to the current object * @return */ private static int nextHashCode() { //Each time a value is created, the hash value calculated by nextHashCode increases by HASH_INCREMENT(0x61c88647) return nextHashCode.getAndAdd(HASH_INCREMENT); } /** * Returns the initial value of the local variable of this thread (that is, the current thread) * This method is called when each thread calls the get() method for the first time (provided that the set method is not called). If it is called * set Method, the method will no longer execute, and each thread can call it at most once, but when an error occurs * You can call again to initialize a starting value. Generally, you need to rewrite this method * @return */ protected T initialValue() { return null; }
2, Construction method
-
An empty parameter structure, nothing done, no analysis, continue to learn
public ThreadLocal() { }
3, Member method
/** * threadLocalHashCode ---> Bucket addressing for threadLocals: * 1.When the thread obtains threadLocal.get(): * If this is the first time to get on a threadLocal object, a value will be assigned to the current object * The value and the current threadLocal object are wrapped into an entry object, where the key of the entry is the threadLocal object * value Is the value generated by threadLocal for the current thread * 2.Which bucket of the threadlocales map of the current thread is this entry stored in? * Bucket addressing is related to the threadLocalHashCode of the current threadLocal object * Use the current threadLocal object threadlocalhashcode & (table.length-1) * The location of the calculation result is the location where the current entry needs to be stored in the Entry[] table */ private final int threadLocalHashCode = nextHashCode(); /** * nextHashCode:Indicates the hash value * This property will be used when creating ThreadLocal object * Each time a threadLocal object is created, a hash value is assigned to the object using nextHashCode */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * HASH_INCREMENT,: Represents the increment of the hash value * For each ThreadLocal object created, ThreadLocal.HASH_INCREMENT(0x61c88647) * This value is very special. It is Fibonacci number, also known as Huang Jian's partition number * hash When the increment is this number, the benefit is that the hash distribution is very uniform */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns a hash value of nextHashCode: * When creating a new ThreadLocal object, this method will assign a hash value to the current object * @return */ private static int nextHashCode() { //Each time a value is created, the hash value calculated by nextHashCode increases by HASH_INCREMENT(0x61c88647) return nextHashCode.getAndAdd(HASH_INCREMENT); } /** * Returns the initial value of the local variable of this thread (that is, the current thread) * This method is called when each thread calls the get() method for the first time (provided that the set method is not called). If it is called * set Method, the method will no longer execute, and each thread can call it at most once, but when an error occurs * You can call again to initialize a starting value. Generally, you need to rewrite this method * @return */ protected T initialValue() { return null; } //The simple null parameter construction method will not be introduced in detail here public ThreadLocal() { } /** * Returns the thread local variable associated with the current thread and the current ThreadLocal object. This variable can only be accessed by the current thread * If the current thread does not allocate a local variable, the initialValue method is used to allocate the initial local variable value * @return */ public T get() { //Get current thread Thread t = Thread.currentThread(); //getMap(t): get the threadLocal object (located in the Thread class) of the current Thread thread object ThreadLocalMap map = getMap(t); //If the condition holds, the current thread already has its own ThreadLocal object if (map != null) { //key: current threadLocal object (this) //Call the map.getEntry() method according to the key to obtain the entry associated with the threadLocal in the threadLocalMap ThreadLocalMap.Entry e = map.getEntry(this); //If the condition holds, (the currently obtained entry is not empty) //Indicates that the current thread has initialized the thread local variable associated with the current threadLocal object if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; //Return value return result; } } //How many situations are there in the implementation? //Case 1: the threadLocalMap corresponding to the current thread is empty (that is, the threadLocalMap object of the current thread has not been initialized) //Case 2: the current thread and the current threadLocal object have not generated the associated thread local variable //The setInitialValue method initializes the value of the thread local variable associated with the current threadLocal object //If the current thread does not have threadLocalMap, it will also initialize the creation of threadLocalMap object and call the createmap (current thread object, value) value return setInitialValue(); } /** * setInitialValue Method initializes the value of the thread local variable associated with the current threadLocal object * If the current thread does not have a threadLocalMap object, the threadLocalMap object will be initialized and created * @return */ private T setInitialValue() { //Call the initialValue method of the current threadLocal object. This method will be overridden in most cases //This method will be called when the thread first calls the get of the threadLocal object (the set method has not been called before) //Generally, it is called only once. When an error occurs, it will be called once //value is the thread local variable associated with the current thread by the current ThreadLocal object T value = initialValue(); //Gets the current thread object Thread t = Thread.currentThread(); //Get threadLocals(threadLocalMap object) inside the current thread ThreadLocalMap map = getMap(t); //If the condition is true: the threadLocalMap object has been initialized inside the thread (threadLocals of the thread will only be initialized once) if (map != null) //Save the current threadLocal and thread local variables generated by the current thread to threadLocalMap map.set(this, value); //If else -- > is executed, the threadlocalMap object inside the current thread has not been initialized else //Here, call the createMap method to create a threadLocalMap object for the current thread: //1. Parameter 1: current thread object //2. The value of the thread's local variable related to the current threadLocal (obtained by calling the initialValue() method) createMap(t, value); //Returns the local variables related to the current thread t and the current threadLocal return value; } /** * Modify the value of the thread local variable associated with the current threadLocal object for the current thread * @param value */ public void set(T value) { //Gets the current thread object Thread t = Thread.currentThread(); //Gets the threadLocalMap object of the current thread ThreadLocalMap map = getMap(t); //If the condition is true: the threadLocalMap of the current thread has been initialized if (map != null) //Call the set method of threadLocalMap object to rewrite or add map.set(this, value); //If else ---- > is executed, the threadLocalMap object inside the current thread has not been initialized else //Here, the createMap method is called to create a ThreadLocalMap object for the current thread //Parameter 1: current thread object t //2. Thread local variables related to the current threadLocal createMap(t, value); } /** * Remove the thread local variable associated with the current threadLocal object from the current thread: */ public void remove() { //Gets the threadLocalMap object of the current thread ThreadLocalMap m = getMap(Thread.currentThread()); //If the condition is true: the current thread has already initialized the threadLocalMap object if (m != null) //Call threadLocalMap.remove(key = current threadLocal) to remove thread local variables m.remove(this); } /** * Gets the threadLocalMap object corresponding to the current thread * ThreadLocalMap In the Thread class, it is a member variable of the Thread * @param t * @return */ ThreadLocalMap getMap(Thread t) { //Returns threadlocales of the current object return t.threadLocals; } /** * Creates a ThreadLocalMap object for the current thread * @param t * @param firstValue */ void createMap(Thread t, T firstValue) { //The meaning of passing t (current thread object) is to access the t.threadLocals field of the current thread and initialize this field //new ThreadLocalmap(this,firstvalue): //Create a threadLocalMap object and initialize k - v as: //Key: This < current threadLocal Object > //value: the local variable of the thread related to the current threadLocal t.threadLocals = new ThreadLocalMap(this, firstValue); }
4. Section
- If you look at ThreadLocal's internal related methods and member properties, it is not difficult, but ThreadLocal is more than that. Its whole kernel is ThreadLocalMap (more complex) in Thread class.
- The source code analysis of ThreadLocalMap will be introduced in the next article!
- Before learning the threadlocalmap kernel, let's sort out the execution process of threadlocalmap:
Process summary:
- Each thread has its own ThreadLocalMap object; (root cause of resource isolation under each ThreadLocal multithread)
- When calling the set(value) setting value of the same ThreadLocal object, each thread sets the value to the member variable Entry[] table of its threadLocalMap object
- As for the subscript position of the current value placed in the array, it is calculated from the threadLocalHashCode of the threadLocal object, that is, the threadLocalHashCode in the multithreaded environment is shared.
- threadLocalHashCode of ThreadLocal object is an atomic self incrementing variable, which is initialized by the class method initValue. That is, when the ThreadLocal object is instantiated, ThreadLocal = new threadlocal(); The value of threadLocalHashCode will be initialized and will not change again. Therefore, the same thread can only save the last set value in the same ThreadLocal object.
- Why does each thread have its own ThreadLocalMap object and is an array?
- According to the above analysis, multiple threads can achieve resource isolation between threads by operating a ThreadLocal object. Array is used because a thread may need to achieve multiple resource isolation through multiple ThreadLocal objects. The threadLocalHashCode of each different ThreadLocal object is different, which is mapped to different subscripts under the ThreadLocalMap object array. (a thread may need multiple shared variables, so it needs multiple ThreadLocal to save a copy of the shared data for each thread, which can explain that the ThreadLocalMap object is an array.)
- The ThreadLocalMap object of each thread solves hash collision by offsetting the position.
- Each thread has its own ThreadLocalMap object and capacity expansion mechanism, which is natural thread safe.
Object, and is it an array?- According to the above analysis, multiple threads can achieve resource isolation between threads by operating a ThreadLocal object. Array is used because a thread may need to achieve multiple resource isolation through multiple ThreadLocal objects. The threadLocalHashCode of each different ThreadLocal object is different, which is mapped to different subscripts under the ThreadLocalMap object array. (a thread may need multiple shared variables, so it needs multiple ThreadLocal to save a copy of the shared data for each thread, which can explain that the ThreadLocalMap object is an array.)
- The ThreadLocalMap object of each thread solves hash collision by offsetting the position.
- Each thread has its own ThreadLocalMap object and capacity expansion mechanism, which is natural thread safe.