Wechat public account: the landlord’s small black road is far away, the future is better learning is boundless, we come on together!
I introduced ThreadLocal in a previous article, and got a general idea of the application scenarios and implementation principles of ThreadLocal.
A simple summary is that each Thread has a threadLocals property, which is a ThreadLocalMap containing an array of entries, the key being a weak reference to the ThreadLocal type, and the value being the value for the pair. All operations are based on this ThreadLocalMap operation.
However, it has a limitation that it cannot be passed between parent and child threads. That is, local thread variables set in the parent thread cannot be accessed in the child thread. Later, a new class called InheritableThreadLocal was introduced to solve this problem.
By using this method, the child thread can access the local thread variables of the parent thread when the child thread is created. The implementation principle is to copy the local thread variables of the parent thread to the local thread variables of the child thread when the parent thread is created.
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
Copy the code
As you can see from the above structure, it mainly overrides the getMap, createMap methods.
There are two important variables in the Thread class
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
Copy the code
# init() method fragment
Thread parent = currentThread();
.
if(inheritThreadLocals && parent.inheritableThreadLocals ! =null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
Copy the code
The child Thread is created by calling the new Thread() method in the parent Thread. The Thread#init method is called in the Thread constructor.
If the inheritableThreadLocals of the parent thread is not empty, If inheritThreadLocals is true(the default is true), the parent thread’s ingerit local variable is used to create the child thread’s inheritableThreadLocals structure, which copies local variables from the parent thread to the child thread.
private Entry[] table;
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if(e ! =null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if(key ! =null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while(table[h] ! =null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
Copy the code
By default, child threads copy parent threads by shallow copy. If a deep copy is required, use a custom ThreadLocal that InheritThreadLocal and override childValue methods.
And its implementation principle is mainly from the parent thread when creating the child thread local variable value. The parent inheritableThreadLocals copied to a child thread, which copies the opportunity is created when the child thread.
[inheritableThreadLocals] [inheritableThreadLocals] copies the value of the parent thread to the child thread when creating the child thread. [inheritableThreadLocals] [inheritableThreadLocals] copies the value of the parent thread to the child thread when creating the child thread. It still records the value of the external thread when the task was first submitted, causing an error in the data.
So how to solve this phenomenon?
By simply copying the parent thread’s context when the user thread submits a task to the thread pool, you can implement pass-through of local variable calls in the thread pool. Based on this idea, Ali proposed the TransmittableThreadLocal class.
Runable
Callable
TtlRunable
TtlCallable
TransmittableThreadLocal
TransmittableThreadLocal
public final void set(T value) {
super.set(value);
if (null == value) removeValue();
else addValue();
}
Copy the code
It first calls the InheritableThreadLocal set method of the parent class, adding value to the inheritableThreadLocals variable of the Thread object. If value is null, the removeValue() method is called, otherwise the addValue method is called.
private void addValue(a) {
if(! holder.get().containsKey(this)) { / / @ 1
holder.get().put(this.null); // WeakHashMap supports null value.
}
}
private void removeValue(a) {
holder.get().remove(this);
}
Copy the code
Calling the addValue method stores the current ThreadLocal to the global static variable Hodler if there is any TransmittableThreadLocal. All the TransmittableThreadLocal objects that are bound to the Thread are stored in this holder. The holder only records which TransmittableThreadLocal objects are bound to the current Thread.
Here is the principle of TtlRunable.
private TtlRunnable(@Nonnull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.capturedRef = new AtomicReference<Object>(capture());
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
Copy the code
Create a Map container to store the local thread variables of the parent thread. The key is the transmittable elocal thread during the parent thread execution. Store the values in the thread. The default is shallow copy, and for deep copy, you override the copyValue method.
public void run(a) {
Object captured = capturedRef.get();
if (captured == null|| releaseTtlValueReferenceAfterRun && ! capturedRef.compareAndSet(captured,null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
Object backup = replay(captured);
try {
runnable.run();
} finally {
restore(backup);
}
}
Copy the code
TtlRunable is implemented in Runable, so the thread pool executes TtlRunable, but in TtlRunnable Run China executes Runable Run.
In the TtlRunable constructor, capture() is called to get all the contexts in the current thread and store them in the AtomicReference.
When the thread is executing, the TtlRunable run method is called. TtlRunable will get the context in the calling thread from the AtomicReference, and use the replay method to copy the context to the current thread, and the context backup.
When the thread is finished, restore is called to pass in the backup context, pass in the backup context, delete the newly added context, and copy the context back to the current thread. This execution does not pollute the original context of the thread in the thread pool.