Hello everyone, I am xiao CAI, a desire to do CAI Not CAI xiao CAI in the Internet industry. Soft but just, soft praise, white piao just! Ghost ~ remember to give me a three – even oh!
This article focuses on the use of ThreadLocal
Refer to it if necessary
If it is helpful, do not forget the Sunday
Wechat public number has been opened, xiao CAI Liang, did not pay attention to the students remember to pay attention to oh!
We talked about the ThreadLocal class and basic usage methods in the concurrent series earlier, so let’s take a look at how ThreadLocal is actually used!
ThreadLocal profile
concept
The ThreadLocal class is used to provide local variables within a thread. This type of variable access in a multi-threaded environment (get and set access) can ensure that the variables of each thread are relatively independent of the variables in other threads. ThreadLocal instances are typically of the private static type and are used to associate threads and contexts.
role
- To transfer data
Provides local variables inside the thread. You can use ThreadLocal to pass public variables in different components on the same thread.
- Threads concurrent
Suitable for multi-threaded concurrency.
- Thread isolation
Variables for each thread are independent and do not affect each other.
ThreadLocal of actual combat
1. Common methods
ThreadLocal ()
Constructor to create a ThreadLocal object
void set (T value)
Sets the local variable of the current thread binding
T get ()
Gets the local variable of the current thread binding
void remove ()
Removes a local variable from the current thread binding
2. Why ThreadLocal
Let’s start with a set of concurrent code scenarios:
@Data
public class ThreadLocalTest {
private String name;
public static void main(String[] args) {
ThreadLocalTest tmp = new ThreadLocalTest();
for (int i = 0; i < 4; i++) {
Thread thread = new Thread(() -> {
tmp.setName(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() +
"\t Get data:" + tmp.getName());
});
thread.setName("Thread-"+ i); thread.start(); }}}Copy the code
Our ideal code output would look like this:
/** OUTPUT **/
Thread-0Get data: Thread-0
Thread-1Get data: Thread-1
Thread-2Get data: Thread-2
Thread-3Get data: Thread-3
Copy the code
But the actual output looks like this:
/** OUTPUT **/
Thread-0Get data: Thread-1
Thread-3Get data: Thread-3
Thread-1Get data: Thread-1
Thread-2Get data: Thread-2
Copy the code
It doesn’t matter if the order is out of order, but we can see that Thread 0 gets thread-1
From the result, we can see that multiple threads accessing the same variable get an exception because the data between threads is not isolated!
Problems with concurrent threads? Then lock it up and we’re done! At this point you write the following code:
@Data
public class ThreadLocalTest {
private String name;
public static void main(String[] args) {
ThreadLocalTest tmp = new ThreadLocalTest();
for (int i = 0; i < 4; i++) {
Thread thread = new Thread(() -> {
synchronized (tmp) {
tmp.setName(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()
+ "\t"+ tmp.getName()); }}); thread.setName("Thread-"+ i); thread.start(); }}}/** OUTPUT **/
Thread-2 Thread-2
Thread-3 Thread-3
Thread-1 Thread-1
Thread-0 Thread-0
Copy the code
As a result, locking seems to solve this problem, but synchronized is more commonly used for multithreaded data sharing than for multithreaded data isolation. The use of synchronized here solves the problem, but it is somewhat inappropriate, and as a heavyweight lock, adding synchronized in order to achieve multithreaded data isolation can also affect performance.
Lock method is also denied, so how to solve? Try ThreadLocal instead:
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public String getName(a) {
return threadLocal.get();
}
public void setName(String name) {
threadLocal.set(name);
}
public static void main(String[] args) {
ThreadLocalTest tmp = new ThreadLocalTest();
for (int i = 0; i < 4; i++) {
Thread thread = new Thread(() -> {
tmp.setName(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() +
"\t Get data:" + tmp.getName());
});
thread.setName("Thread-"+ i); thread.start(); }}}Copy the code
Before looking at the output, let’s take a look at what happens to the code
First we add a private static ThreadLocal, and then with setName we’re actually storing data in a ThreadLocal, and with getName we’re fetching data in a ThreadLocal. It feels easy to operate, but does this really separate data between threads?
/** OUTPUT **/
Thread-1Get data: Thread-1
Thread-2Get data: Thread-2
Thread-0Get data: Thread-0
Thread-3Get data: Thread-3
Copy the code
It can be seen from the result that each thread can fetch the corresponding data. ThreadLocal has also solved the problem of data isolation between multiple threads.
So to summarize, why use ThreadLocal and what distinguishes it from synchronized
- synchronized
How it works: Synchronization uses a “time for space” approach, providing only one variable for different threads to queue up for access
Emphasis: Multiple threads access resources synchronously
- ThreadLocal
How ThreadLocal works: Each thread is given a copy of a variable in a “space for time” manner, allowing simultaneous access without interference
Emphasis: In multithreading, data between each thread is isolated from each other
3. Internal structure
From the example above we can see that the two main methods of ThreadLocal are set() and get()
Let’s just wonder how we would design ThreadLocal, and whether we would have an idea: Each ThreadLocal creates a Map, uses the thread as the Map key, and stores local variables as the Map value, thus achieving the effect of local variable isolation for each thread.
This is also true, as ThreadLocal was designed this way in the early days, but the design has changed since JDK 8, as follows:
Design process:
-
Each Thread Thread has an internal ThreadLocalMap
-
ThreadLocalMap stores ThreadLocal objects as keys and thread variables as values
-
The Map inside a Thread is maintained by a ThreadLocal, which is responsible for setting and retrieving Thread variable values from the Map
-
For different threads, each time the copy value is obtained, other threads cannot obtain the copy value of the thread, so that the copy will form isolation, mutual interference
Note: Each thread must have its own map, but this class is a normal Java class, does not implement the Map interface, but has map-like functions.
It looks like it’s going to be more complicated than we thought, but what’s the benefit of doing that?
- The number of entries stored in each Map will be smaller because the number of entries stored was previously determined by the number of threads, but is now determined by the number of ThreadMaps. In practice, the number of ThreadLocal will be even smaller than the number of threads.
- When a Thread is destroyed, the corresponding ThreadLocalMap is also destroyed, reducing memory usage
4. Source code analysis
Let’s start by looking at what members ThreadLocalMap has:
If you’ve seen the source code for HashMap, you’ll be familiar with these:
- INITIAL_CAPACITY: indicates the initial capacity, which must be a power of 2
- Table: table for storing data
- Size: Indicates the number of entries in the array, which is used to determine whether the current usage of the table exceeds the threshold
- Threshold: threshold for capacity expansion. If the table usage exceeds the threshold, capacity expansion will be performed
ThreadLocals
A type of Thread class ThreadLocal. ThreadLocalMap ThreadLocals types of variables, the each Thread is used to store private data.
ThreadLocalMap
ThreadLocalMap is an internal class of ThreadLocal. Each data is stored as an Entry, which is stored as a key-value pair that references the ThreadLocal.
We can see that Entry inherits WeakReference, because if it is a strong reference, even if ThreadLocal is set to null, GC will not reclaim it because ThreadLocalMap has a strong reference to it.
ThreadRef -> CurrentThread -> threadLocalMap -> Entry The Entry will not be reclaimed (the Entry contains ThreadLocal instances and values), resulting in an Entry memory leak.
If you use weak references, there will be no memory leaks, which is also incorrect.
If the key in the Entry is null and there is no strong reference to the threaLocal instance, threadLocal will be collected by gc, but the value will not be collected. The value will never be accessed, resulting in a memory leak
Let’s take a look at the core methods of ThreadLocalMap:
Set method
First let’s take a look at the source code:
public void set(T value) {
// Get the current thread object
Thread t = Thread.currentThread();
// Get the ThreadLocalMap object maintained in this thread object
ThreadLocalMap map = getMap(t);
// Check whether the map exists
if(map ! =null)
// If it exists, call map.set to set this entity entry
map.set(this, value);
else
If no ThreadLocalMap exists in the current thread, createMap is called to initialize the ThreadLocalMap
// Store t(the current thread) and value(the value of t) as the first entry into ThreadLocalMap
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
// This is the threadLocal that calls this method
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code
Execution process:
-
First get the current thread, and get a map based on the current thread
-
If the map obtained is not empty, the parameter is set to the map (the reference to the current ThreadLocal is the key)
-
If the Map is empty, the Map is created for the thread and the initial value is set
The get method
The source code is as follows:
public T get(a) {
// Get the current thread object
Thread t = Thread.currentThread();
// Get the ThreadLocalMap object maintained in this thread object
ThreadLocalMap map = getMap(t);
// If the map exists
if(map ! =null) {
// Call getEntry to obtain the storage entity e with the current ThreadLocal as the key
ThreadLocalMap.Entry e = map.getEntry(this);
// void e
if(e ! =null) {
@SuppressWarnings("unchecked")
// Get the value of the storage entity e
// Is the value of this ThreadLocal that we want for the current thread
T result = (T)e.value;
returnresult; }}return setInitialValue();
}
private T setInitialValue(a) {
// Call initialValue to get the initialized value
// This method can be overridden by subclasses and returns null by default if not overridden
T value = initialValue();
// Get the current thread object
Thread t = Thread.currentThread();
// Get the ThreadLocalMap object maintained in this thread object
ThreadLocalMap map = getMap(t);
// Check whether the map exists
if(map ! =null)
// If it exists, call map.set to set this entity entry
map.set(this, value);
else
If no ThreadLocalMap exists in the current thread, createMap is called to initialize the ThreadLocalMap
// Store t(the current thread) and value(the value of t) as the first entry into ThreadLocalMap
createMap(t, value);
// Return the set value value
return value;
}
Copy the code
Execution process:
- First get the current thread, and get a map based on the current thread
- If the obtained map is not empty, use the reference of ThreadLocal as the key to obtain the corresponding Entry in the map. Otherwise, go to step 4
- If Entry Entry is not empty, Entry. Value is returned. Otherwise, go to step 4
- If the map is empty or the entry is empty, the initialValue function obtains the initialValue, and then creates a new map with a reference to ThreadLocal and value as the firstKey and firstValue
The remove method
The source code is as follows:
public void remove(a) {
// Get the ThreadLocalMap object maintained in the current thread object
ThreadLocalMap m = getMap(Thread.currentThread());
// If the map exists
if(m ! =null)
// If it exists, map.remove is called
m.remove(this);
}
// Delete the corresponding entity entry using the current ThreadLocal as the key
private void remove(ThreadLocal
key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return; }}}Copy the code
Execution process:
- First get the current thread, and get a map based on the current thread
- If the map obtained is not empty, the entry corresponding to the current ThreadLocal object is removed
The initialValue method
The source code is as follows:
protected T initialValue(a) {
return null;
}
Copy the code
This method is called when a thread accesses its ThreadLocal for the first time through get (). InitialValue () will not be called unless the set () method is called first. Normally, this method is called at most once.
If we want a ThreadLocal thread-local variable to have an initial value other than NULL, we must override this method by subclassing ThreadLocal, which can be done through an anonymous inner class.
“The END”
That’s it for ThreadLocal, and I hope you found it useful.
The road is long, xiao CAI seeks together with you!
Today you work harder, tomorrow you will be able to say less words!
I am xiao CAI, a man who studies with you. 💋
Wechat public number has been opened, xiao CAI Liang, did not pay attention to the students remember to pay attention to oh!