In the last article, we talked about InheritableThreadLocal, which solves the problem that ThreadLocal cannot share context with parent threads. But we’ve probably heard about Ali’s open source product TransmittableThreadLocal, so what does it do?
Sharing in the thread pool
If we use InheritableThreadLocal, we can use the InheritableThreadLocal thread to pass the context of the generating task thread to the executing thread. Without further ado, let’s test the code directly:
public class InheritableThreadLocalContext { private static InheritableThreadLocal<Context> context = new InheritableThreadLocal<>(); static class Context { String name; int value; } public static void main (String [] args) {/ / fixed thread pool ExecutorService ExecutorService = Executors. NewFixedThreadPool (4); for (int i = 1; i <= 10; i++) { int finalI = i; Context contextMain = new Context(); context contextMain = new context (); contextMain.name = String.format("Thread%s name", finalI); contextMain.value = finalI * 20; InheritableThreadLocalContext.context.set(contextMain); For (int j = 1; j <= 10; j++) { System.out.println("Thread" + finalI + " produce task " + (finalI * 20 + j)); ExecutorService. Execute (() - > {/ / mission child thread Context contextChild = InheritableThreadLocalContext. Context. The get (); System.out.println(Thread.currentThread().getName() + " execute task, name : " + contextChild.name + " value : " + contextChild.value); }); } } ).start(); }}}Copy the code
The desired result is that the output of the child thread corresponds to that of the parent thread. However, the actual result is not expected, let me rearrange the results:
Thread1 produce task 21 // omit 8 rows Thread1 produce task 30 Thread2 produce task 41 // omit 8 rows Thread2 produce task 50 Pool-1 thread-1 execute task, name: Thread2 name value: 40 // Omit 47 line pool-1 thread-1 execute task, name: Thread2 name value : 40 Thread3 produce task 61 // Omit 8 rows of Thread3 produce task 70 Thread4 produce task 81 // omit 8 rows of Thread4 produce task 90 Thread5 produce task 101 // delete 8 rows Thread5 produce task 110 Thread6 produce task 121 // delete 8 rows Thread6 produce task 130 Thread7 produce task 150 pool1-thread-2 execute task, name: Thread7 name value: 140 // Omit 6 lines pool-1-thread-2 execute task, name: Thread7 name value: 140 Thread8 produce task 161 // delete 8 rows Thread9 produce task 181 // delete 8 rows Thread9 produce task 190 pool-1-thread-4 execute task, name : Thread9 name value : 180 pool-1-thread-4 execute task, name : Thread9 name value : Thread10 produce task 210 pool-1-thread-3 execute task, name: Thread10 name value: 200 // Omit 39 lines pool-1-thread-3 execute task, name: Thread10 name value: 200Copy the code
Although total production and total consumption are both 100, it is clear that some consume more and some consume less. A reasonable guess is that the child thread is created after the main thread is put into the task. To test this hypothesis, generate the thread pool with ThreadPoolExecutor and assign the context and start all threads before generating tasks with child threads:
Public static void main(String[] args) {// executorService = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>() ); // Assign Context to the main thread Context = new Context(); context.name = "Thread0 name"; context.value = 0; InheritableThreadLocalContext.context.set(context); All threads / / open the executorService. PrestartAllCoreThreads (); for (int i = 1; i <= 10; i++) { int finalI = i; Context contextMain = new Context(); context contextMain = new context (); contextMain.name = String.format("Thread%s name", finalI); contextMain.value = finalI * 20; InheritableThreadLocalContext.context.set(contextMain); For (int j = 1; j <= 10; j++) { System.out.println("Thread" + finalI + " produce task " + (finalI * 20 + j)); ExecutorService. Execute (() - > {/ / mission child thread Context contextChild = InheritableThreadLocalContext. Context. The get (); System.out.println(Thread.currentThread().getName() + " execute task, name : " + contextChild.name + " value : " + contextChild.value); }); } } ).start(); }}Copy the code
As expected, the thread executing the task outputs the values set by the outermost main thread.
So how can we achieve the desired effect in the first place? How do you make it possible for the executor thread to get the context of the caller thread when using a thread pool to perform a task?
Solve with TransmittableThreadLocal
The above problem is mainly because the threads that perform tasks are managed by thread pools and can be reused (you can call it pooled reuse). If the parent thread has the context TransmittableThreadLocal, there is a problem. If the parent thread has the context TransmittableThreadLocal, there is no context TransmittableThreadLocal. If the parent thread has the context TransmittableThreadLocal, there is no context TransmittableThreadLocal.
The TransmittableThreadLocal is a tool class provided by Ali that mainly solves the above problems. So how do you use it?
First, you need to introduce appropriate dependencies:
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> The < version > 2.11.0 < / version > < / dependency >Copy the code
If there is any TransmittableThreadLocal (if there is any TransmittableThreadLocal), there is no TransmittableThreadLocal (if there is any TransmittableThreadLocal).
public class TransmittableThreadLocalTest { private static TransmittableThreadLocal<Context> context = new TransmittableThreadLocal<>(); static class Context { String name; int value; } public static void main (String [] args) {/ / fixed thread pool ExecutorService ExecutorService = Executors. NewFixedThreadPool (4); for (int i = 1; i <= 10; i++) { int finalI = i; Context contextMain = new Context(); context contextMain = new context (); contextMain.name = String.format("Thread%s name", finalI); contextMain.value = finalI * 20; TransmittableThreadLocalTest.context.set(contextMain); For (int j = 1; j <= 10; j++) { System.out.println("Thread" + finalI + " produce task " + (finalI * 20 + j)); Runnable task = () - > {/ / mission of the child thread Context contextChild = TransmittableThreadLocalTest. Context. The get (); System.out.println(Thread.currentThread().getName() + " execute task, name : " + contextChild.name + " value : " + contextChild.value); }; TtlRunnable Runnable ttlRunnable = ttLRunnable.get (task); executorService.execute(ttlRunnable); } } ).start(); }}}Copy the code
If you run it again, you will see that the output of the executing thread is completely corresponding to that of the calling thread. Of course, I have modified the Runnable method. Ali also provides the thread pool method, which is simple as follows:
Public static void main (String [] args) {/ / fixed thread pool ExecutorService ExecutorService = Executors. NewFixedThreadPool (4); / / additional processing, the modification of the object the executorService executorService = TtlExecutors. GetTtlExecutorService (executorService); ExecutorService finalExecutorService = executorService; for (int i = 1; i <= 10; i++) { int finalI = i; Context contextMain = new Context(); context contextMain = new context (); contextMain.name = String.format("Thread%s name", finalI); contextMain.value = finalI * 20; TransmittableThreadLocalTest.context.set(contextMain); For (int j = 1; j <= 10; j++) { System.out.println("Thread" + finalI + " produce task " + (finalI * 20 + j)); Runnable task = () - > {/ / mission of the child thread Context contextChild = TransmittableThreadLocalTest. Context. The get (); System.out.println(Thread.currentThread().getName() + " execute task, name : " + contextChild.name + " value : " + contextChild.value); }; finalExecutorService.execute(task); } } ).start(); }}Copy the code
There are more simple writing, you can refer to the github:https://github.com/alibaba/transmittable-thread-local
conclusion
As a matter of fact, the two articles about the update of ThreadLocal are both due to the sharing meeting about TTL of a department on Wednesday and the introduction of TransmittableThreadLocal. However, ctrip is facing the change of internationalization. The current language information is definitely the most convenient to store in a thread context, but it involves thread delivery (because asynchronous interfaces are called, etc.), so this is a natural consideration. In terms of performance, they did some tests, but I was just a listener, I didn’t use it, and we could talk about it.
If you are interested, you can visit my blog or pay attention to my public number and headline number. Maybe there will be unexpected surprises.
death00.github.io/
Public account: The road to health