Hello, THIS is Yes.
I’ve looked at ThreadLocal, InheritableThreadLocal, and FastThreadLocal.
If there is any context TransmittableThreadLocal(TTL), there is no context TransmittableThreadLocal. If there is any context TransmittableThreadLocal, there is no context TransmittableThreadLocal.
Let’s introduce TTL today to complement the ThreadLocal family’s shortcomings.
After this post, ThreadLocal is really out of the question!
However, it is advisable to read the preamble (link at the end of the article), otherwise it may be difficult to understand.
The reason
There must be a reason for the appearance of any component, and it can be better understood if we know the background of the reason.
We know that ThreadLocal was created to localize thread resources and prevent unnecessary multithreading competition.
In some scenarios, when a parent thread creates a new child thread and wants to inherit its ThreadLocal to the child thread, an InheritableThreadLocal is used to pass localized resources to the parent thread.
If an InheritableThreadLocal is available in the parent Thread when the child Thread is initialized, the InheritableThreadLocal is available when the child Thread is initialized. If an InheritableThreadLocal is available when the child Thread is initialized, Copy the InheritableThreadLocal of the parent thread to the child thread.
This simply passes ThreadLocal data from the parent thread to the child thread.
However, this scenario can only happen with new Threads! That is, when the thread is created manually! There is a problem with that, in ordinary times we basically use thread pools.
We can’t use InheritableThreadLocal when all the threads in the pool are pre-created.
So the requirement is, how does ** pass ThreadLocal to threads in the thread pool? **, the JDK library does not have this functionality, so what?
We’ll have to build the wheels ourselves.
How to design?
The requirements are clear, but how to achieve them?
When we use thread pools, for example, if you want to submit a task, we use the following code:
Runnable task = newRunnable.... ; executorService.submit(task);Copy the code
Tip: The following ThreadLocal refers to thread-local data, not the ThreadLocal class
At this point, we want to pass the ThreadLocal of the current thread to the thread in the thread pool that will execute the task, but how do we know which thread in the thread pool will execute the task?
So, we need to save the current thread’s ThreadLocal to the task, and then when A thread in the thread pool, such as thread A, gets the task to execute, we need to see if there are any ThreadLocal stored in the task. If it does, put the ThreadLocal in thread A’s local variable, and the pass is complete.
The next step, which is also critical, is to restore the context of the thread in the thread pool. That is, after the task is completed, the local data brought by the task is deleted, and the local data of the thread is restored.
The design idea should be clear, right? Let’s see how to do it!
How to do that?
A simple, straightforward translation of the above design into code is as follows:
If you’ve read my previous analysis of ThreadLocal, you should be able to easily understand this.
This can be achieved, but the operability is too poor and the coupling is too high.
So we have to think about how to optimize it, and there’s actually a design pattern that works, and that’s the decorator pattern.
We can create our own Runnable class, such as YesRunnable, and then assign the threadLocal of the current thread in the constructor when new YesRunnable is created.
And then the run method is also decorated, so let’s go straight to the pseudocode:
public YesRunnable(Runnable runable) { this.threadlocalCopy = copyFatherThreadlocal(); this.runable = runable; } public void run() {// the parent threadLocal Object backup = setThreadlocal(threadlocalCopy); // The parent threadLocal Object backup = setThreadlocal(threadlocalCopy); Try {runable. The run (); // Execute the decorated task logic} finally {restore(backup); // Restore the current thread context}}Copy the code
The usage is as follows:
Runnable task = () -> {... }; YesRunnable yesRunnable =new YesRunnable(task);
executorService.submit(yesRunnable);
Copy the code
You see, this does not realize our above design!
One thing that hasn’t been revealed, however, is how to implement copyFatherThreadlocal.
How do we know what Threadlocal the parent thread has now? And which ones need context passing?
So we also need to create a class that inherits Threadlocal, such as YesThreadlocal, and declare variables that indicate parent-child passes!
public class YesThreadlocal<T> extends ThreadLocal<T>
Copy the code
And then we need somewhere to store all YesThreadlocal used by the current parent thread context so that we can iterate over the copy when we copyFatherThreadlocal, right?
We can create a holder to hold the YesThreadlocal, but the holder variable also needs to be thread isolated. After all, each thread uses a different YesThreadlocal, so we need to use ThreadLocal.
Then YesThreadlocal may have a lot, we can use set to save, but in order to prevent the risk of memory leakage caused by the holder, we need weak reference to it, but there is no WeakHashSet, so we will use WeakHashMap instead of store.
private static finalThreadLocal<WeakHashMap<YesThreadLocal<Object>, ? >> holder =new.Copy the code
This creates a variable that is thread-specific and can be used to store all YesThreadLocal used by the current thread for future replication without causing memory leaks (weak references).
Do you feel a little confused for a while? Ok, let’s go ahead and see how to use this hold, maybe it’ll be a little clearer.
First we replace the local variables that need to be passed to the thread pool with YesThreadLocal.
Then rewrite the set method as follows:
@Override public final void set(T value) { super.set(value); Call ThreadLocal's set addThisToHolder(); // Stuff the current YesThreadLocal object into the hold. } private void addThisToHolder() { if (! holder.get().containsKey(this)) { holder.get().put((YesThreadLocal <Object>) this, null); }}Copy the code
Now that you have all the YesThreadLocal used in the holder, let’s see how copyFatherThreadlocal should be implemented.
private static HashMap<YesThreadLocal <Object>, Object> copyFatherThreadlocal () {
HashMap<YesThreadLocal <Object>, Object> fatherMap = new HashMap<YesThreadLocal <Object>, Object>();
for (YesThreadLocal <Object> threadLocal : YesThreadLocal.holder.get().keySet()) {
fatherMap .put(threadLocal, threadLocal.copyValue());
}
return fatherMap ;
}
Copy the code
The logic is simple: a map traversal copy.
I now use a paragraph to summarize, the above all operations combined to understand, should be a lot clearer.
Summary implementation ideas
- Create a new YesThreadLocal class that inherits from ThreadLocal, and the variable used to identify the modifier needs to be copied by the parent thread
- Create a new YesRunnable class that inherits from Runnable and uses decorator mode so that you don’t have to modify the original Runnable. Copy the parent thread’s YesThreadLocal variable to one of YesRunnable’s member variables, threadlocalCopy, during construction. Decorates the run method by assigning threadlocalCopy to the context of the current executing thread before the actual logic executes, saving the context prior to the current thread, and restoring the context of this thread after execution
- Since we need to copy all YesThreadLocal used by the parent thread at construction time, we need a holder variable to hold all YesThreadLocal used so that we can iterate over the assignment during construction. In addition, the holder variable also needs thread isolation, so it is decorated with ThreadLocal, and to prevent memory leaks caused by strong holder references, it is stored with WeakHashMap.
- The time to add YesThreadLocal to holder is at YesThreadLocal#set
The realization of the TransmittableThreadLocal
This article only deals with the core idea of TTL (critical path). Due to the lack of space, the rest will not be expanded, and a detailed article will be written later.
My implementation above is actually a copy of TTL. If you understand the implementation above, the introduction to TTL should be a simple and refresher.
Let’s take a quick look at how TTL is used.
Easy to use, right?
The value of YesThreadLocal is InheritableThreadLocal, because the immediate new TTL also has the passability of the parent thread’s local variable.
Let’s take a look at the TTL get and set operations:
AddThisToHolder: addThisToHolder: addThisToHolder: addThisToHolder: addThisToHolder: addThisToHolder: addThisToHolder: addThisToHolder: addThisToHolder
So, after the parent thread assigns, or executes the set operation, the holder in the parent thread stores the current TTL object, which is the ttL.set () operation in the code shown above.
TtlRunnable (TtlRunnable) : TtlRunnable (TtlRunnable) : TtlRunnable (TtlRunnable) : TtlRunnable (TtlRunnable) : TtlRunnable (TtlRunnable)
Let’s look at its construction method:
CapturedRef is a copy of the parent thread’s local variable, and capture() is the same as copyFatherThreadlocal().
Let’s look again at the run method for TtlRunnable decorations:
Logical four steps:
- Get a copy of the parent local variable
- Assign to the current thread (a thread in the thread pool) and save the previous local variable
- Perform logical
- Restores local variables prior to the current thread
Let’s look at the capture() method again, that is, how it is copied.
In THE TTL, a static tool class is specifically defined as a Transmitter to achieve the above operations: Capture, replay, restore.
A snapshot is a snapshot of the TTL stored in the holder, and a new map is returned.
There is also captureThreadLocalValues, which is used for compatibility with classes that cannot change ThreadLocal to TTL but want to copy the values passed by ThreadLocal.
Let’s take a look at replay, which assigns a local variable from a parent class to the current thread.
The logic is very clear, first backup, then copy overwrite, finally return backup, copy overwrite setTtlValuesTo code is very simple:
The for loop does a set, and you can see why we need to remove the TTL that the parent thread doesn’t have, because we’re just doing a set. If you do not remove the local variable of the current thread, it is not completely inherited from the parent thread, and may be contaminated with previous local variables, so it is better to remove it.
Finally, let’s look at the restore operation:
By now, the principle of TTL should be very clear!
Some use
Just one of the uses we showed above is to wrap Runnable with TtlRunnable. Get.
TTL also provides thread pool closure-like methods, such as TTlexecTrue:
ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
Copy the code
In fact, the principle is very simple, decorated thread pool submission methods, implementation of TtlRunnable. Get wrapper
A more transparent way of using Java Agents to dress up the JDK’s thread pool implementation classes is almost pointless to use.
In the Java launch parameters and: – javaagent: path/to/transmittable – thread – local – 2. X.Y.J ar can, then the normal use, the native thread pool implementation class has already been changed quietly!
TransmittableThreadLocal<String> ttl= new TransmittableThreadLocal<>();
ExecutorService executorService = Executors.newFixedThreadPool(1);
Runnable task = new RunnableTask();
executorService.submit(task);
Copy the code
The last
Ok, so that’s all about TTL and how to explain it.
The core operations are Capture/Replay/Restore (CRR), copying snapshots, replaying snapshots, and restoring context.
Some people might wonder why undo is necessary. Every time a thread in the thread pool executes, if it uses TTL, the context of the thread executing will be overwritten. There is no need to undo, right?
In fact, some people also put this question to the author and the answer is:
- If the thread pool is full and the thread pool rejection policy is CallerRunsPolicy, the executing thread becomes the current thread, which must be undone, otherwise the context is gone.
- In the case of a ForkJoinPool (with the Stream and CompletableFuture executed in parallel, and the underlying ForkJoinPool used), the extended ForkJoinTask is executed directly in the calling thread.
There are many more details to be said about TTL, but space is limited, and the details need to be covered in another chapter. However, today’s article also covers the core idea of TTL.
Suppose an interviewer asks you, “How do I pass ThreadLocal to a thread pool?” Surely you can answer it
Well, later I will post a TTL details ~ wait for me.
Welcome to pay attention to my public number [yes training level strategy], more articles, waiting for you to read!
I’m yes, from a little bit to a billion bits. See you next time.