亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解Spring Cloud中Hystrix 線程隔離導(dǎo)致ThreadLocal數(shù)據(jù)丟失

 更新時(shí)間:2018年03月26日 10:27:30   作者:尹吉?dú)g  
這篇文章主要介紹了詳解Spring Cloud中Hystrix 線程隔離導(dǎo)致ThreadLocal數(shù)據(jù)丟失,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

在Spring Cloud中我們用Hystrix來(lái)實(shí)現(xiàn)斷路器,Zuul中默認(rèn)是用信號(hào)量(Hystrix默認(rèn)是線程)來(lái)進(jìn)行隔離的,我們可以通過(guò)配置使用線程方式隔離。

在使用線程隔離的時(shí)候,有個(gè)問(wèn)題是必須要解決的,那就是在某些業(yè)務(wù)場(chǎng)景下通過(guò)ThreadLocal來(lái)在線程里傳遞數(shù)據(jù),用信號(hào)量是沒(méi)問(wèn)題的,從請(qǐng)求進(jìn)來(lái),但后續(xù)的流程都是通一個(gè)線程。

當(dāng)隔離模式為線程時(shí),Hystrix會(huì)將請(qǐng)求放入Hystrix的線程池中去執(zhí)行,這個(gè)時(shí)候某個(gè)請(qǐng)求就有A線程變成B線程了,ThreadLocal必然消失了。

下面我們通過(guò)一個(gè)簡(jiǎn)單的列子來(lái)模擬下這個(gè)流程:

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());
  }
}

我們?cè)谥黝愔卸x了一個(gè)ThreadLocal用來(lái)傳遞數(shù)據(jù),然后起了一個(gè)線程,在線程中調(diào)用Service中的call方法,并且往Threadlocal中設(shè)置了一個(gè)值,在Service中獲取ThreadLocal中的值,然后再調(diào)用Dao中的call方法,也是獲取ThreadLocal中的值,我們運(yùn)行下看效果:

Service:Thread-0
Service:猿天地
==========================
Dao:Thread-0
Dao:猿天地

可以看到整個(gè)流程都是在同一個(gè)線程中執(zhí)行的,也正確的獲取到了ThreadLocal中的值,這種情況是沒(méi)有問(wèn)題的。

接下來(lái)我們改造下程序,進(jìn)行線程切換,將調(diào)用Dao中的call重啟一個(gè)線程執(zhí)行:

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());
  }
}

再次運(yùn)行,看效果:

Service:Thread-0
Service:猿天地
==========================
Dao:Thread-1
Dao:null

可以看到這次的請(qǐng)求是由2個(gè)線程共同完成的,在Service中還是可以拿到ThreadLocal的值,到了Dao中就拿不到了,因?yàn)榫€程已經(jīng)切換了,這就是開(kāi)始講的ThreadLocal的數(shù)據(jù)會(huì)丟失的問(wèn)題。

那么怎么解決這個(gè)問(wèn)題呢,其實(shí)也很簡(jiǎn)單,只需要改一行代碼即可:

static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

將ThreadLocal改成InheritableThreadLocal,我們看下改造之后的效果:

Service:Thread-0
Service:猿天地
==========================
Dao:Thread-1
Dao:猿天地

值可以正常拿到,InheritableThreadLocal就是為了解決這種線程切換導(dǎo)致ThreadLocal拿不到值的問(wèn)題而產(chǎn)生的。

要理解InheritableThreadLocal的原理,得先理解ThreadLocal的原理,我們稍微簡(jiǎn)單的來(lái)介紹下ThreadLocal的原理:

每個(gè)線程都有一個(gè) ThreadLocalMap 類型的 threadLocals 屬性,ThreadLocalMap 類相當(dāng)于一個(gè)Map,key 是 ThreadLocal 本身,value 就是我們?cè)O(shè)置的值。

public class Thread implements Runnable {
  ThreadLocal.ThreadLocalMap threadLocals = null;
}

當(dāng)我們通過(guò) threadLocal.set(“猿天地”); 的時(shí)候,就是在這個(gè)線程中的 threadLocals 屬性中放入一個(gè)鍵值對(duì),key 是 當(dāng)前線程,value 就是你設(shè)置的值猿天地。

public void set(T value) {
  Thread t = Thread.currentThread();
  ThreadLocalMap map = getMap(t);
  if (map != null)
    map.set(this, value);
  else
    createMap(t, value);
}

當(dāng)我們通過(guò) threadlocal.get() 方法的時(shí)候,就是根據(jù)當(dāng)前線程作為key來(lái)獲取這個(gè)線程設(shè)置的值。

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();
}

通過(guò)上面的介紹我們可以了解到threadlocal能夠傳遞數(shù)據(jù)是用Thread.currentThread()當(dāng)前線程來(lái)獲取,也就是只要在相同的線程中就可以獲取到前方設(shè)置進(jìn)去的值。

如果在threadlocal設(shè)置完值之后,下步的操作重新創(chuàng)建了一個(gè)線程,這個(gè)時(shí)候Thread.currentThread()就已經(jīng)變了,那么肯定是拿不到之前設(shè)置的值。具體的問(wèn)題復(fù)現(xiàn)可以參考上面我的代碼。

那為什么InheritableThreadLocal就可以呢?

InheritableThreadLocal這個(gè)類繼承了ThreadLocal,重寫(xiě)了3個(gè)方法,在當(dāng)前線程上創(chuàng)建一個(gè)新的線程實(shí)例Thread時(shí),會(huì)把這些線程變量從當(dāng)前線程傳遞給新的線程實(shí)例。

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);
  }
}

通過(guò)上面的代碼我們可以看到InheritableThreadLocal 重寫(xiě)了childValue, getMap,createMap三個(gè)方法,當(dāng)我們往里面set值的時(shí)候,值保存到了inheritableThreadLocals里面,而不是之前的threadLocals。

關(guān)鍵的點(diǎn)來(lái)了,為什么當(dāng)創(chuàng)建新的線程池,可以獲取到上個(gè)線程里的threadLocal中的值呢?原因就是在新創(chuàng)建線程的時(shí)候,會(huì)把之前線程的inheritableThreadLocals賦值給新線程的inheritableThreadLocals,通過(guò)這種方式實(shí)現(xiàn)了數(shù)據(jù)的傳遞。

源碼最開(kāi)始在Thread的init方法中,如下:

if (parent.inheritableThreadLocals != null)
  this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

createInheritedMap如下:

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
  }

賦值代碼:

 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++;
        }
      }
    }
}

到此為止,通過(guò)inheritableThreadLocals我們可以在父線程創(chuàng)建子線程的時(shí)候?qū)ocal中的值傳遞給子線程,這個(gè)特性已經(jīng)能夠滿足大部分的需求了,但是還有一個(gè)很?chē)?yán)重的問(wèn)題是如果是在線程復(fù)用的情況下就會(huì)出問(wèn)題,比如線程池中去使用inheritableThreadLocals 進(jìn)行傳值,因?yàn)閕nheritableThreadLocals 只是會(huì)再新創(chuàng)建線程的時(shí)候進(jìn)行傳值,線程復(fù)用并不會(huì)做這個(gè)操作,那么要解決這個(gè)問(wèn)題就得自己去擴(kuò)展線程類,實(shí)現(xiàn)這個(gè)功能。

不要忘記我們是做Java的哈,開(kāi)源的世界有你需要的任何東西,下面我給大家推薦一個(gè)實(shí)現(xiàn)好了的Java庫(kù),是阿里開(kāi)源的transmittable-thread-local。

GitHub地址:https://github.com/alibaba/transmittable-thread-local

主要功能就是解決在使用線程池等會(huì)緩存線程的組件情況下,提供ThreadLocal值的傳遞功能,解決異步執(zhí)行時(shí)上下文傳遞的問(wèn)題。

JDK的InheritableThreadLocal類可以完成父線程到子線程的值傳遞。但對(duì)于使用線程池等會(huì)緩存線程的組件的情況,線程由線程池創(chuàng)建好,并且線程是緩存起來(lái)反復(fù)使用的;這時(shí)父子線程關(guān)系的ThreadLocal值傳遞已經(jīng)沒(méi)有意義,應(yīng)用需要的實(shí)際上是把 任務(wù)提交給線程池時(shí)的ThreadLocal值傳遞到任務(wù)執(zhí)行時(shí)。

transmittable-thread-local使用方式分為三種,修飾Runnable和Callable,修飾線程池,Java Agent來(lái)修飾JDK線程池實(shí)現(xiàn)類

接下來(lái)給大家演示下線程池的修飾方式,首先來(lái)一個(gè)非正常的案例,代碼如下:

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("猿天地"+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());
  }
}

運(yùn)行上面的代碼出現(xiàn)的結(jié)果是不正確的,輸出結(jié)果如下:

Dao:猿天地99
Dao:猿天地99
Dao:猿天地99
Dao:猿天地99
Dao:猿天地99
Dao:猿天地99
Dao:猿天地99
Dao:猿天地99
Dao:猿天地99
Dao:猿天地99
Dao:猿天地99
Dao:猿天地99
Dao:猿天地99

正確的應(yīng)該是從1到100,由于線程的復(fù)用,值被替換掉了才會(huì)出現(xiàn)不正確的結(jié)果

接下來(lái)使用transmittable-thread-local來(lái)改造有問(wèn)題的代碼,添加transmittable-thread-local的Maven依賴:

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>transmittable-thread-local</artifactId>
  <version>2.2.0</version>
</dependency>

只需要修改2個(gè)地方,修飾線程池和替換InheritableThreadLocal:

static TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
static ExecutorService pool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));

正確的結(jié)果如下:

Dao:猿天地85
Dao:猿天地84
Dao:猿天地86
Dao:猿天地87
Dao:猿天地88
Dao:猿天地90
Dao:猿天地89
Dao:猿天地91
Dao:猿天地93
Dao:猿天地92
Dao:猿天地94
Dao:猿天地95
Dao:猿天地97
Dao:猿天地96
Dao:猿天地98
Dao:猿天地99

到這里我們就已經(jīng)可以完美的解決線程中,線程池中ThreadLocal數(shù)據(jù)的傳遞了,各位看官又疑惑了,標(biāo)題不是講的Spring Cloud中如何解決這個(gè)問(wèn)題么,我也是在Zuul中發(fā)現(xiàn)這個(gè)問(wèn)題的,解決方案已經(jīng)告訴大家了,至于怎么解決Zuul中的這個(gè)問(wèn)題就需要大家自己去思考了,后面有時(shí)間我再分享給大家。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論