In Spring Cloud, we use Hystrix to implement circuit breakers. In Zuul, semaphores are used by default (Hystrix is threaded by default). We can use thread isolation through configuration.
When using thread isolation, there is a problem that must be solved, that is, in some business scenarios, data is passed in threads through ThreadLocal. It is fine to use semaphores, and it comes in from the request, but the subsequent process is all about one thread.
When the isolation mode is threaded, Hystrix will put the request into the Hystrix thread pool for execution. At this time, a request will become a thread B, and ThreadLocal will inevitably disappear.
Let’s simulate this process through a simple column:
public class CustomThreadLocal { static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { CustomThreadLocal.threadLocal.set("Apes"); new Service().call(); } }).start(); }}class Service { public void call() { System.out.println("Service:" + Thread.currentThread().getName()); System.out.println("Service:" + CustomThreadLocal.threadLocal.get()); new Dao().call(); }}class Dao { public void call() { System.out.println("===================================================================================================================================================================================================================================================================================================================================================================================================================================================== CustomThreadLocal.threadLocal.get()); }}We define a ThreadLocal in the main class to pass data, then a thread is created, and the call method in the Service is called in the thread, and a value is set in Threadlocal, and the value in ThreadLocal is obtained in the Service, and then the call method in Dao is called, which is also obtained in ThreadLocal. Let's run it to see the effect:
Service:Thread-0
Service: Apes and Heaven
====================================
Dao:Thread-0
Dao:The world of the world
You can see that the entire process is executed in the same thread, and the value in ThreadLocal has been correctly obtained. There is no problem in this situation.
Next, we transform the program, perform thread switching, and call the call in Dao to restart a thread execution:
public class CustomThreadLocal { static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { CustomThreadLocal.threadLocal.set("Apes"); new Service().call(); } }).start(); }}class Service { public void call() { System.out.println("Service:" + Thread.currentThread().getName()); System.out.println("Service:" + CustomThreadLocal.threadLocal.get()); //new Dao().call(); new Thread(new Runnable() { @Override public void run() { new Dao().call(); } }).start(); }}class Dao { public void call() { System.out.println("========================================================================================================================================================================================================================================================================================================================================================================================================== System.out.println("Dao:" + Thread.currentThread().getName()); System.out.println("Dao:" + CustomThreadLocal.threadLocal.get()); }}Run again to see the effect:
Service:Thread-0
Service: Apes and Heaven
====================================
Dao:Thread-1
Dao:null
You can see that this request was completed by two threads. You can still get the value of ThreadLocal in the Service. You can't get it in Dao because the thread has been switched. This is the problem that the data of ThreadLocal will be lost at the beginning.
So how to solve this problem is actually very simple, you only need to change one line of code:
static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
Change ThreadLocal to InheritableThreadLocal, let's take a look at the effect after the transformation:
Service:Thread-0
Service: Apes and Heaven
====================================
Dao:Thread-1
Dao:The world of the world
The value can be obtained normally. InheritableThreadLocal is caused by solving the problem that ThreadLocal cannot get the value due to this thread switching.
To understand the principle of InheritableThreadLocal, we must first understand the principle of ThreadLocal. Let’s briefly introduce the principle of ThreadLocal:
Each thread has a threadLocals property of type ThreadLocalMap. The ThreadLocalMap class is equivalent to a Map. The key is ThreadLocal itself, and the value is the value we set.
public class Thread implements Runnable { ThreadLocal.ThreadLocalMap threadLocals = null;} When we pass threadLocal.set("Apes and World");, we put a key-value pair in the threadLocals property in this thread. The key is the current thread, and the value is the value you set.
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value);} When we use the threadlocal.get() method, we obtain the value set by this thread based on the current thread as the key.
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();}Through the above introduction, we can understand that threadlocal can pass data by using Thread.currentThread() to obtain it, that is, as long as it is in the same thread, the value set in front can be obtained.
If the next operation recreates a thread after the threadlocal has set the value, and Thread.currentThread() has changed at this time, then you will definitely not get the value you set before. For specific reproduction of problems, please refer to my code above.
Then why is InheritableThreadLocal OK?
The InheritableThreadLocal class inherits ThreadLocal and rewritten 3 methods. When creating a new thread instance Thread on the current thread, these thread variables will be passed from the current thread to the new thread instance.
public class InheritableThreadLocal<T> extends ThreadLocal<T> { /** * Computes the child's initial value for this inheritable thread-local * variable as a function of the parent's value at the time the child * thread is created. This method is called from within the parent * thread before the child is started. * <p> * This method merely returns its input argument, and should be overridden * if a different behavior is desired. * * @param parentValue the parent thread's value * @return the child thread's initial value */ protected T childValue(T parentValue) { return parentValue; } /** * Get the map associated with a ThreadLocal. * * @param t the current thread */ ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } /** * Create the map associated with a ThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the table. */ void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }}Through the above code, we can see that InheritableThreadLocal rewritten the three methods childValue, getMap, and createMap. When we set the value in it, the value is saved in inheritableThreadLocals, instead of the previous threadLocals.
The key point is here. Why can we get the value in threadLocal in the previous thread when creating a new thread pool? The reason is that when a new thread is created, the inheritableThreadLocals of the previous thread will be assigned to the inheritableThreadLocals of the new thread, which realizes data transmission in this way.
The source code is initially in the Thread init method, as follows:
if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
createInheritedMap as follows:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }Assignment code:
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++; } } }}Up to this point, through inheritableThreadLocals, we can pass the value in Local to the child thread when the parent thread creates the child thread. This feature can meet most needs, but there is another serious problem that if it is thread reuse, there will be problems. For example, if it is thread reuse, it will use inheritableThreadLocals in the thread pool to pass values, because inheritableThreadLocals will only pass values when creating a new thread. ThreadReuse will not do this operation. So to solve this problem, you have to extend the thread class by yourself to implement this function.
Don’t forget that we are doing Java. The open source world has everything you need. Below I recommend a Java library that has been implemented, which is Alibaba’s open source transmittable-thread-local.
GitHub address: https://github.com/alibaba/transmittable-thread-local
The main function is to solve the problem of ThreadLocal value delivery when using thread pools and other components that cache threads, and solve the problem of context delivery during asynchronous execution.
JDK's InheritableThreadLocal class can complete the value passing of the parent thread to the child thread. However, for the case where the thread pool is used and other components that cache threads, the thread is created by the thread pool, and the thread is cached and used repeatedly; at this time, it is meaningless to pass the ThreadLocal value of the parent-child thread relationship. What the application needs is actually to pass the ThreadLocal value when submitting the task to the thread pool to the task execution.
Transmittable-thread-local usage methods are divided into three types: Modify Runnable and Callable, modify thread pool, and Java Agent to modify JDK thread pool implementation classes
Next, let’s demonstrate the modification method of thread pool. First, let’s take an abnormal case, the code is as follows:
public class CustomThreadLocal { static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>(); static ExecutorService pool = Executors.newFixedThreadPool(2); public static void main(String[] args) { for(int i=0;i<100;i++) { int j = i; pool.execute(new Thread(new Runnable() { @Override public void run() { CustomThreadLocal.threadLocal.set("Ape World"+j); new Service().call(); } })); } }}class Service { public void call() { CustomThreadLocal.pool.execute(new Runnable() { @Override public void run() { new Dao().call(); } }); }}class Dao { public void call() { System.out.println("Dao:" + CustomThreadLocal.threadLocal.get()); }}The result of running the above code is incorrect, and the output is as follows:
Dao: Ape World 99
Dao: Ape World 99
Dao: Ape World 99
Dao: Ape World 99
Dao: Ape World 99
Dao: Ape World 99
Dao: Ape World 99
Dao: Ape World 99
Dao: Ape World 99
Dao: Ape World 99
Dao: Ape World 99
Dao: Ape World 99
Dao: Ape World 99
The correct one should be from 1 to 100. Due to thread reuse, the value will be replaced only if it is replaced.
Next, use transmittable-thread-local to transform the problematic code and add the Maven dependency of transmittable-thread-local:
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.2.0</version></dependency>
Just modify 2 places, modify the thread pool and replace InheritableThreadLocal:
static TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();static ExecutorService pool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
The correct results are as follows:
Dao: Ape World 85
Dao: Apes and World 84
Dao: Apes and World 86
Dao: Apes and World 87
Dao: Apes and World 88
Dao: Ape World 90
Dao: Apes and World 89
Dao: Ape World 91
Dao: Ape World 93
Dao: Ape World 92
Dao: Ape World 94
Dao: Ape World 95
Dao: Ape World 97
Dao: Ape World 96
Dao: Ape World 98
Dao: Ape World 99
At this point, we can perfectly solve the transmission of ThreadLocal data in thread pools. Dear readers are puzzled again. The title is not about how to solve this problem in Spring Cloud. I also found this problem in Zuul. The solution has been told to everyone. As for how to solve this problem in Zuul, everyone needs to think about it yourself. I will share it with you if you have time later.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.