When reading Spring source code, I noticed that a class ThreadLocal appears many times. In fact, ThreadLocal is widely used, not only in Spring, but also in Mybatis. It may also be seen in the business code of some projects.
In fact, it is a thread-local variable, but because most business programming cases are not used, so we may be unfamiliar, now analyze, so that when we see in the code, it will not hinder our reading.
Use the sample
/ / sample
// Create and assign an initial value
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "baseStr");
/ / thread 1
new Thread(() -> {
// Prints the default values
System.out.println(threadLocal.get());
/ / assignment
threadLocal.set(Thread.currentThread().getName());
/ / print the value
System.out.println(threadLocal.get());
}).start();
/ / thread 2
new Thread(() -> {
// Prints the default values
System.out.println(threadLocal.get());
/ / assignment
threadLocal.set(Thread.currentThread().getName());
/ / print the value
System.out.println(threadLocal.get());
}).start();
Copy the code
The output value:
The above code mainly shows the operations of creating, initializing, getting, and assigning values. It is very simple to use.
The following main analysis of more complex can do, how to achieve the problem.
Analysis of the characteristics of
ThreadLocal: Get set remove initialValue: get Set Remove initialValue: Get Set Remove initialValue
The source code defines it as follows:
This class provides thread-local variables. These variables differ from their normal counterparts because each thread that accesses one (through its GET or set methods) has its own, independently initialized copy of the variable. ThreadLocal instances typically want to bind private static fields in thread-specific classes (for example, user ID or transaction ID).Copy the code
In other words, the main purpose is to share attributes in a thread. How does this work? Take a look at the following sample code to slowly analyze:
/ / sample 2
ThreadLocal<HashMap<String, Object>> threadLocal = ThreadLocal.withInitial(() -> new HashMap<>());
new Thread(() -> {
HashMap<String, Object> map = threadLocal.get();
map = map == null ? new HashMap<>() : map;
map.put("current1", Thread.currentThread().getName());
threadLocal.set(map);
System.out.println("t1:" + JSONObject.toJSONString(threadLocal.get()));
}).start();
new Thread(() -> {
HashMap<String, Object> map = threadLocal.get();
map = map == null ? new HashMap<>() : map;
map.put("current2", Thread.currentThread().getName());
threadLocal.set(map);
System.out.println("t2:" + JSONObject.toJSONString(threadLocal.get()));
}).start();
System.out.println("main:" + JSONObject.toJSONString(threadLocal.get()));
Copy the code
The output value:
Thread 1 and thread 2 have the same code that prints the same value of the same threadLocal object. There is no interaction between the two threads. In particular, the first sentence of thread 2 (printing the default value), after thread 1 has already operated on the threadLocal object, The printed values are still the default values of the initialization, so there is no mutual contamination between the two threads.
This can also be seen as a feature: variable sharing within threads
The main idea of implementation is that we define a ThreadLocal between multiple threads, each thread will create a copy, each copy is owned by the thread, so that multiple threads do not interfere with each other.
ThreadLocal does not define variables to store values, so where do we store values of sets? First, throw out the concept, and then look at the code:
ThreadLocal only maintains the mapping of values, which are stored in the threadLocals field of the Thread class
Based on the above concept, it is easy to understand that since the values exist in the Thread class of each Thread, it is normal to have isolation between threads.
The following is a concrete way to verify this concept.
Set method
First look at the source code:
public void set(T value) {
// Get the current thread
Thread t = Thread.currentThread();
// Get the object to store the value
ThreadLocalMap map = getMap(t);
if(map ! =null)
// if not null, set value
map.set(this, value);
else
// If not initialized, initialized
createMap(t, value);
}
Copy the code
The following points are involved:
-
GetMap gets the value, from where
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } Copy the code
As can be seen intuitively, ThreadLocalMap is obtained from Thread.
-
Why does createMap only initialize when it gets a value? What does initialization do
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } Copy the code
Very simply, a ThreadLocalMap object is created and assigned to threadLocals, where the main input is: the first input to the constructor of ThreadLocalMap is the current object reference of ThreadLocal
-
ThreadLocalMap stores the structure of the value
ThreadLocalMap is a custom hash map that uses WeakReferences(Java WeakReferences) as a key (Entity extends WeakReference)
ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// Create entry * table is an array table = new Entry[INITIAL_CAPACITY]; // Calculate the subscript and assign the value int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); // Record the number of entries size = 1; // Adjust the capacity threshold setThreshold(INITIAL_CAPACITY); } Copy the code
As you can see, the table is created only when ThreadLocalMap has its first value to store, indicating that it is lazily created.
ThreadLocalMap uses WeakReferences as a key because of the nature of the Entry
static class Entry extends WeakReference<ThreadLocal<? >> { Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; }}Copy the code
As you can see, Entry extends WeakReference(Java WeakReference), and the constructor that constructs Entry, key, is a ThreadLocal object
WeakReference related knowledge is involved here, which will not be explained too much here
The get method
public T get(a) {
// Get the current thread
Thread t = Thread.currentThread();
// Get the ThreadLocalMap object
ThreadLocalMap map = getMap(t);
if(map ! =null) {
// If it is not null, the value is obtained
ThreadLocalMap.Entry e = map.getEntry(this);
if(e ! =null) {
// The associated value is returned if the value is not null
@SuppressWarnings("unchecked")
T result = (T)e.value;
returnresult; }}// Initialize if ThreadLocalMap is empty or the corresponding Entity is empty
return setInitialValue();
}
Copy the code
GetMap (t) : getMap(t) : getMap(t) : getMap(t) : getMap(t) : getMap(t) : getMap(t)
-
Gets the Entry and the value associated with the Entry
For example, map.getentry (this) uses a reference to the current ThreadLocal itself.
-
What does the setInitialValue method do
private T setInitialValue(a) {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
return value;
}
Copy the code
SetInitialValue is a variant of set, so it is basically the same as set, but there are still some differences, as follows:
-
-
Value Specifies the value
InitialValue here can override itself. When we manually specify the initialValue (we only need to override the initialValue method), if the value is not set at the first fetch, InitialValue is initialized using the return value of our overridden initialValue method, which by default returns NULL.
protected T initialValue(a) { return null; } Copy the code
-
-
- There is a return value, which is the value returned by initialValue
Now that the main features of ThreadLocal have been shown, let’s examine a few common methods to get a more complete view of the ThreadLocal class.
The remove method
public void remove(a) {
ThreadLocalMap m = getMap(Thread.currentThread());
if(m ! =null)
m.remove(this);
}
Copy the code
The remove method is used to remove the value of the current set. After removing it, the initialValue method is reinitialized by getting it again
The initialValue method
Introduced in the get() method, here are two common methods of overwriting
- Anonymous inner class
ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue(a) {
return "base"; }};Copy the code
- withInitial
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "baseStr");
Copy the code
WithInitial method
Threadlocal. withInitial overwrites initialValue. Analyze:
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
Copy the code
SuppliedThreadLocal = SuppliedThreadLocal = SuppliedThreadLocal = SuppliedThreadLocal = SuppliedThreadLocal = SuppliedThreadLocal
// Extends ThreadLocal
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
// Overrides the initialValue method of ThreadLocal
@Override
protected T initialValue(a) {
returnsupplier.get(); }}Copy the code
SuppliedThreadLocal extends ThreadLocal and overwrites the initialValue method, which corresponds to our analysis above. WithInitial (() -> “baseStr”));
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Copy the code
Supplier is a functional interface, then
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "baseStr");
Copy the code
Is equivalent to
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() { @Override public String get() { return "baseStr"; }});Copy the code
Now it seems a lot more logical
This has nothing to do with ThreadLocal and is only mentioned to prevent lambda expressions from interfering with reading analysis
Later, some classic use cases will be listed, and the principle analysis will be recorded here.