1. 程式人生 > >Spring Cloud中使用Hystrix 執行緒隔離導致ThreadLocal資料丟失

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能為我們解決不少的問題。