<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官再問你 ThreadLocal,就這樣狠狠 “懟” 回去!

          共 9067字,需瀏覽 19分鐘

           ·

          2021-03-25 12:13

          本文大綱

          1. 用過 ThreadLocal 嗎?在什么場景下會使用 ThreadLocal
          2. 講講 ThreadLocal 的原理吧!
          3. 使用 ThreadLocal 有什么需要注意的嗎?
          4. 有什么方式能提高 ThreadLocal 的性能嗎?
          5. 如何將 ThreadLocal 的數(shù)據(jù)傳遞到子線程中?
          6. 線程池中如何實現(xiàn) ThreadLocal 的數(shù)據(jù)傳遞?

          用過 ThreadLocal 嗎?在什么場景下會使用 ThreadLocal。

          這個回答一定要足夠自信:必須用過啊,無論是在平時的業(yè)務(wù)開發(fā)過程中會用到,其他很多三方框架中也都用到了 ThreadLocal。

          如果你回答沒用過,很有可能就涼涼了,因為 ThreadLocal 在很多場景都能用到,假如實在沒用過也不要沒信心,看完這篇文章你就知道如何回答了。

          場景一:ThreadLocal+MDC 實現(xiàn)鏈路日志增強(qiáng)

          日志增強(qiáng)之前也寫過一篇文章,講解了實現(xiàn)的功能,細(xì)節(jié)沒有講,可以看看下面這篇文章了解。

          文章:有了鏈路日志增強(qiáng),排查 Bug 小意思啦!

          比如我們需要在整個鏈路的日志中輸出當(dāng)前登錄的用戶 ID,首先就得在攔截器獲取過濾器中獲取用戶 ID,然后將用戶 ID 進(jìn)行存儲到 ThreadLocal。

          然后再層層進(jìn)行透傳,如果用的 Dubbo,那么就在 Dubbo 的 Filter 中進(jìn)行傳遞到下一個服務(wù)中。問題來了,在 Dubbo 的 Filter 中如何獲取前面存儲的用戶 ID 呢?

          答案就是 ThreadLocal。獲取后添加到 MDC 中,就可以在日志中輸出用戶 ID。

          場景二:ThreadLocal 實現(xiàn)線程內(nèi)的緩存,避免重復(fù)調(diào)用

          緩存這塊就不重復(fù)講了,之前有單獨(dú)寫過文章,大家直接看之前的文章就可以了。

          文章:簡直騷操作,ThreadLocal 還能當(dāng)緩存用

          場景三:ThreadLocal 實現(xiàn)數(shù)據(jù)庫讀寫分離下強(qiáng)制讀主庫

          首先你的項目中要做了讀寫分離,如果有對讀寫分離不了解的同學(xué)可以查看這篇文章:讀寫分離

          某些業(yè)務(wù)場景下,必須保證數(shù)據(jù)的及時性。主從同步有延遲,可以使用強(qiáng)制讀主庫來保證數(shù)據(jù)的一致性。

          在 Sharding JDBC 中,有提供對應(yīng)的 API 來設(shè)置強(qiáng)制路由到主庫,具體代碼如下:

          HintManager hintManager = HintManager.getInstance();
          hintManager.setMasterRouteOnly();

          HintManager 中就使用了 ThreadLocal 來存儲相關(guān)信息。這樣就可以實現(xiàn)在業(yè)務(wù)代碼中設(shè)置路由信息,在底層的數(shù)據(jù)庫路由那塊獲取信息,實現(xiàn)優(yōu)雅的數(shù)據(jù)傳遞。

          public final class HintManager implements AutoCloseable {
          private static final ThreadLocal<HintManager> HINT_MANAGER_HOLDER = new ThreadLocal();
          // ...............
          }

          場景四:ThreadLocal 實現(xiàn)同一線程下多個類之間的數(shù)據(jù)傳遞

          在 Spring Cloud Zuul 中,過濾器是必須要用的。用過濾器我們可以實現(xiàn)權(quán)限認(rèn)證,日志記錄,限流等功能。

          過濾器有多個,而且是按順序執(zhí)行的。過濾器之前要透傳數(shù)據(jù)該如何處理?

          Zuul 中已經(jīng)提供了 RequestContext 來實現(xiàn)數(shù)據(jù)傳遞,比如我們在進(jìn)行攔截的時候會使用下面的代碼告訴負(fù)責(zé)轉(zhuǎn)發(fā)的過濾器不要進(jìn)行轉(zhuǎn)發(fā)操作。

          RequestContext.getCurrentContext().setSendZuulResponse(false);

          RibbonRoutingFilter 中就可以通過 RequestContext 獲取對應(yīng)的信息。

          @Override
          public boolean shouldFilter() {
          RequestContext ctx = RequestContext.getCurrentContext();
          return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
          && ctx.sendZuulResponse());
          }

          RequestContext 中就用了 ThreadLocal。

          public class RequestContext extends ConcurrentHashMap<String, Object> {
          protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
          @Override
          protected RequestContext initialValue() {
          try {
          return contextClass.newInstance();
          } catch (Throwable e) {
          throw new RuntimeException(e);
          }
          }
          };
          // .........................
          }

          講講 ThreadLocal 的原理吧!

          ThreadLocal 在使用的時候是單獨(dú)創(chuàng)建對象的,更像一個全局的容器。但是大家有沒有想過一個問題,就是為啥要設(shè)計 ThreadLocal 這個類,而不使用 HashMap 這樣的容器類?

          ThreadLocal 本質(zhì)上是要解決線程之間數(shù)據(jù)的隔離,以達(dá)到互不影響的目的。如果我們用一個 Map 做數(shù)據(jù)存儲,Key 為線程 ID, Value 為你要存儲的內(nèi)容,其實也是能達(dá)到隔離的效果。

          沒錯,效果是能達(dá)到,但是性能就不一定好了,涉及到多個線程進(jìn)行數(shù)據(jù)操作。如果你不看 ThreadLocal 的源碼,你肯定也會以為 ThreadLocal 就是這么實現(xiàn)的。

          ThreadLocal 在設(shè)計這塊很巧妙,會在 Thread 類中嵌入一個 ThreadLocalMap,ThreadLocalMap 就是一個容器,用于存儲數(shù)據(jù)的,但它在 Thread 類中,也就說存儲的就是這個 Thread 類專享的數(shù)據(jù)。

          原本我們以為的 ThreadLocal 設(shè)置值的代碼:

          public void set(T value) {
          Thread t = Thread.currentThread();
          ThreadLocal.put(t.getId(), 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)前線程對象,然后從當(dāng)前線程中獲取線程的 ThreadLocalMap,值是添加到這個 ThreadLocalMap 中的,key 就是當(dāng)前 ThreadLocal 的對象。從使用的 API 看上去像是把值存儲在了 ThreadLocal 中,其實值是存儲在線程內(nèi)部,然后關(guān)聯(lián)了對應(yīng)的 ThreadLocal,這樣通過 ThreadLocal.get 時就能獲取到對應(yīng)的值。

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

          來張圖感受下:

          使用 ThreadLocal 有什么需要注意的嗎?

          • 避免跨線程異步傳遞,雖然有解決方案,文末介紹了方案
          • 使用時記得及時 remove, 防止內(nèi)存泄露
          • 注釋說明使用場景,方便后人
          • 對性能有極致要求可以參考開源框架的做法,用一些優(yōu)化后的類,比如 FastThreadLocal

          有什么方式能提高 ThreadLocal 的性能嗎?

          這個問題其實是考察你對其他的一些框架的了解,因為在一些開源的框架中也有使用 ThreadLocal 的場景,但是這些框架為了讓性能更好,一般都會做一些優(yōu)化。

          比如 Netty 中就重寫了一個 FastThreadLocal 來代替 ThreadLocal,性能在一定場景下比 ThreadLocal 更好。

          性能提升主要表現(xiàn)在如下幾點:

          • FastThreadLocal 操作數(shù)據(jù)的時候,會使用下標(biāo)的方式在數(shù)組中進(jìn)行查找來代替 ThreadLocal 通過哈希的方式進(jìn)行查找。
          • FastThreadLocal 利用字節(jié)填充來解決偽共享問題。

          其實除了 Netty 中對 ThreadLocal 進(jìn)行了優(yōu)化,自定義了 FastThreadLocal。在其他的框架中也有類似的優(yōu)化,比如 Dubbo 中就 InternalThreadLocal,根據(jù)源碼中的注釋,也是參考了 FastThreadLocal 的設(shè)計,基本上差不多。

          如何將 ThreadLocal 的數(shù)據(jù)傳遞到子線程中?

          InheritableThreadLocal 可以將值從當(dāng)前線程傳遞到子線程中,但這種場景其實用的不多,我相信很多人都沒怎么聽過 InheritableThreadLocal。

          那為什么 InheritableThreadLocal 就可以呢?

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

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

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

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

          源碼最開始在 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++;
                          }
                      }
                  }
          }

          線程池中如何實現(xiàn) ThreadLocal 的數(shù)據(jù)傳遞?

          如果涉及到線程池使用 ThreadLocal, 必然會出現(xiàn)問題。首先線程池的線程是復(fù)用的,其次,比如你從 Tomcat 的線程到自己的業(yè)務(wù)線程,也就是跨線程池了,線程也就不是之前的那個線程了,也就是說 ThreadLocal 就用不了,那么如何解決呢?

          可以使用阿里的 ttl 來解決,之前我也寫過一篇文章,可以查看:Spring Cloud 中 Hystrix 線程隔離導(dǎo)致 ThreadLocal 數(shù)據(jù)丟失

          貼上 ttl 的鏈接:https://github.com/alibaba/transmittable-thread-local

          ttl 是基于代碼方式的改造,下面再給大家介紹一種不用改造代碼的方式,基于 Java Agent 來實現(xiàn)的,牛的一批。

          鏈接:https://github.com/Nepxion/DiscoveryAgent

          關(guān)于作者:尹吉?dú)g,簡單的技術(shù)愛好者,《Spring Cloud 微服務(wù)-全棧技術(shù)與案例解析》, 《Spring Cloud 微服務(wù) 入門 實戰(zhàn)與進(jìn)階》作者, 公眾號猿天地發(fā)起人。

          后臺回復(fù) 學(xué)習(xí)資料 領(lǐng)取學(xué)習(xí)視頻


          如有收獲,點個在看,誠摯感謝


          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本三级片网站久射 | 北条麻妃无码一区三区 | 青青草无码黄色电影 | 久久综合无码内射国产 | 亚洲国产欧美日韩在线观看 |