ThreadLocal source code analysis, threadlocal source code
We know that a process is the smallest unit of resource allocation by the OS, while a thread is the smallest unit of Operation execution and does not enjoy resources. ThreadLocal stores thread data variables locally, and each thread stores its own variables. All threads use the same ThreadLocal <T> object to access variables, however, each thread sees different variable values during access, which does not affect the variables of other threads and can be null.
The architecture diagram of the entire implementation is as follows:
A thread can hold multiple ThreadLocal objects. Each ThreadLocal instance is lightweight and only stores the hash value assigned to it by hashCounter and a weak reference of itself. During access, you only need to copy the obj array in values to the local variables of the current method.
In the original JDK, table is implemented by map; In the 1.7 source code, an object array is used to store <ThreadLocal, T>, and kv is stored at intervals; this avoids concurrent thread lock operations and greatly speeds up access. It is also worth mentioning that the hash value assigned by hashCounter to the ThreadLocal object each time is an even number. In this way, the obtained index location stores the ThreadLocal object, and the index + 1 position stores the variable value, which is very clever.
The ThreadLocal class structure is as follows. We will analyze these methods in sequence:
ValuesStatic internal class is equivalent to a global variable, which maintains an array of objects so that each thread can access it.
Public ThreadLocal (){}The constructor is empty.
Public T get ()Returns the variable value stored in the current thread.
Protected T initialValue ()Return null. Rewrite this method as needed.
Public void set (T value)Set the variable value stored in the current thread
Public void remove ()Delete the variable value stored in the current thread
Values initializeValues (Thread current)Create the values object corresponding to the current process
Values values (Thread current)Obtains the values instance of the current process.
After introducing the entire architecture, let's not look at the values static internal class. In fact, it maintains the threading from ThreadLocal to the variable value, just a hashtable. Let's look back at it.
Let's take a look at the get method to complete the variable value of the current thread:
Public T get () {// Optimized for the fast path. thread currentThread = Thread. currentThread (); // get the current thread instance Values values = values (currentThread); // get the value of the current thread instance if (values! = Null) {Object [] table = values. table; int index = hash & values. mask; if (this. reference = table [index]) {return (T) table [index + 1] ;}} else {values = initializeValues (currentThread ); // if the values corresponding to the current thread is empty, create a} return (T) values. getAfterMiss (this );}
Method, obtain values according to the current thread instance. First, let's see what if values is empty?
If values is empty, initialize it and call the initializeValues method:
/*** Creates Values instance for this thread and variable type. */Values initializeValues (Thread current) {return current. localValues = new Values (); // new values instance}
Let's take a look at the construction method of Values:
Values () {initializeTable (INITIAL_SIZE); // INITIAL_SIZE is 16 this. size = 0; // The number of entries stored in the table. this. tombstones = 0; // Number of discarded entries}
Use the initializeTable method to create an object array with a capacity of 32 and a mask value of 0x1F.
Private void initializeTable (int capacity) {this. table = new Object [capacity * 2]; // creates a table with the given initial capacity, an obj array this. mask = table. length-1; // The previously specified capacity must be a power of 2. Here, the default length is 31, this. clean = 0; this. maximumLoad = capacity * 2/3; // 2/3 max load factor}
Return to the get method again, and the method returns getAfterMiss (this). this method passes in the current ThreadLocal and returns the value defined by initialValue (). this method can be customized and overwritten.
If values is not empty, copy the table to the current method variable for operation. Because each ThreadLocal object has a fixed hash value, there is no thread concurrency problem.
The same is true for other operations in ThreadLocal. After the operation is complete, we need to interact with the array in Values. The put method is called here:
/*** Sets entry for given ThreadLocal to given value, creating an * entry if necessary. */void put (ThreadLocal <?> Key, Object value) {cleanUp (); // the discarded elements are cleared first. // The Keep track of first tombstone. that's where we want to go back // and add an entry if necessary. int firstTombstone =-1; for (int index = key. hash & mask; index = next (index) {Object k = table [index]; if (k = key. reference) {// Replace existing entry. table [index + 1] = value; return;} if (k = null) {if (firstTombstone =-1) {// Fill in null slot. table [index] = key. reference; table [index + 1] = value; size + +; return;} // Go back and replace first tombstone. table [firstTombstone] = key. reference; table [firstTombstone + 1] = value; tombstones --; size ++; return;} // Remember first tombstone. if (firstTombstone =-1 & k = TOMBSTONE) {firstTombstone = index ;}}}
Each time you operate on the object array, you must first clear the discarded elements. Then, store the elements.
Summary
The combination design of ThreadLocal and values enables multiple threads to store local variables without interfering with each other. What's more, the lock operation is avoided by allocating fixed hash values. The maintenance of the object array in Values is complicated and will be further researched and supplemented in the future.