Spring Cloud中使用Hystrix 執行緒隔離導致ThreadLocal資料丟失
在Spring Cloud中我們用Hystrix來實現斷路器,預設是用訊號量來進行隔離的,我們可以通過配置使用執行緒方式隔離。
在使用執行緒隔離的時候,有個問題是必須要解決的,那就是在某些業務場景下通過ThreadLocal來線上程裡傳遞資料,用訊號量是沒問題的,從請求進來,但後續的流程都是通一個執行緒。
當隔離模式為執行緒時,Hystrix會將請求放入Hystrix的執行緒池中去執行,這個時候某個請求就有A執行緒變成B執行緒了,ThreadLocal必然消失了。
下面我們通過一個簡單的列子來模擬下這個流程:
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("猿天地");
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("==========================");
System.out .println("Dao:" + Thread.currentThread().getName());
System.out.println("Dao:" + CustomThreadLocal.threadLocal.get());
}
}
我們在主類中定義了一個ThreadLocal用來傳遞資料,然後起了一個執行緒,線上程中呼叫Service中的call方法,並且往Threadlocal中設定了一個值
在Service中獲取ThreadLocal中的值,然後再呼叫Dao中的call方法,也是獲取ThreadLocal中的值,我們執行下看效果:
Service:Thread-0
Service:猿天地
==========================
Dao:Thread-0
Dao:猿天地
可以看到整個流程都是在同一個執行緒中執行的,也正確的獲取到了ThreadLocal中的值,這種情況是沒有問題的。
接下來我們改造下程式,進行執行緒切換,將呼叫Dao中的call重啟一個執行緒執行:
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("猿天地");
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());
}
}
再次執行,看效果:
Service:Thread-0
Service:猿天地
==========================
Dao:Thread-1
Dao:null
可以看到這次的請求是由2個執行緒共同完成的,在Service中還是可以拿到ThreadLocal的值,到了Dao中就拿不到了,因為執行緒已經切換了,這就是開始講的ThreadLocal的資料會丟失的問題。
那麼怎麼解決這個問題呢,其實也很簡單,只需要改一行程式碼即可:
static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
將ThreadLocal改成InheritableThreadLocal,我們看下改造之後的效果:
Service:Thread-0
Service:猿天地
==========================
Dao:Thread-1
Dao:猿天地
值可以正常拿到,InheritableThreadLocal就是為了解決這種執行緒切換導致ThreadLocal拿不到值的問題而產生的
至於原理大家可以去看jdk的原始碼,這邊就不做過多講解了,有了InheritableThreadLocal能為我們解決不少的問題。