<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,談?wù)凢astThreadLocal為啥能這么快?

          共 16055字,需瀏覽 33分鐘

           ·

          2021-09-15 16:27


          往期熱門文章:

          1、手寫了一個簡單的JSON解析器,網(wǎng)友直乎:牛!

          2、代碼寫的垃圾被嫌棄?

          3、Redis的這些拓展方案

          4、MySQL的自增 ID 用完了,怎么辦?

          5、多線程環(huán)境下,HashMap為什么會出現(xiàn)死循環(huán)?


          1 FastThreadLocal的引入背景和原理簡介

          既然jdk已經(jīng)有ThreadLocal,為何netty還要自己造個FastThreadLocal?FastThreadLocal快在哪里?

          這需要從jdk ThreadLocal的本身說起。如下圖:

          在java線程中,每個線程都有一個ThreadLocalMap實例變量(如果不使用ThreadLocal,不會創(chuàng)建這個Map,一個線程第一次訪問某個ThreadLocal變量時,才會創(chuàng)建)。

          該Map是使用線性探測的方式解決hash沖突的問題,如果沒有找到空閑的slot,就不斷往后嘗試,直到找到一個空閑的位置,插入entry,這種方式在經(jīng)常遇到hash沖突時,影響效率。

          FastThreadLocal(下文簡稱ftl)直接使用數(shù)組避免了hash沖突的發(fā)生,具體做法是:每一個FastThreadLocal實例創(chuàng)建時,分配一個下標index;分配index使用AtomicInteger實現(xiàn),每個FastThreadLocal都能獲取到一個不重復(fù)的下標。

          當調(diào)用ftl.get()方法獲取值時,直接從數(shù)組獲取返回,如return array[index],如下圖:

          2 實現(xiàn)源碼分析

          根據(jù)上文圖示可知,ftl的實現(xiàn),涉及到InternalThreadLocalMap、FastThreadLocalThread和FastThreadLocal幾個類,自底向上,我們先從InternalThreadLocalMap開始分析。

          InternalThreadLocalMap類的繼承關(guān)系圖如下:

          2.1 UnpaddedInternalThreadLocalMap的主要屬性

          static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();
          static final AtomicInteger nextIndex = new AtomicInteger();
          Object[] indexedVariables;

          數(shù)組indexedVariables就是用來存儲ftl的value的,使用下標的方式直接訪問。nextIndex在ftl實例創(chuàng)建時用來給每個ftl實例分配一個下標,slowThreadLocalMap在線程不是ftlt時使用到。

          2.2 InternalThreadLocalMap分析

          InternalThreadLocalMap的主要屬性:

          // 用于標識數(shù)組的槽位還未使用
          public static final Object UNSET = new Object();
          /**
           * 用于標識ftl變量是否注冊了cleaner
           * BitSet簡要原理:
           * BitSet默認底層數(shù)據(jù)結(jié)構(gòu)是一個long[]數(shù)組,開始時長度為1,即只有l(wèi)ong[0],而一個long有64bit。
           * 當BitSet.set(1)的時候,表示將long[0]的第二位設(shè)置為true,即0000 0000 ... 0010(64bit),則long[0]==2
           * 當BitSet.get(1)的時候,第二位為1,則表示true;如果是0,則表示false
           * 當BitSet.set(64)的時候,表示設(shè)置第65位,此時long[0]已經(jīng)不夠用了,擴容處long[1]來,進行存儲
           *
           * 存儲類似 {index:boolean} 鍵值對,用于防止一個FastThreadLocal多次啟動清理線程
           * 將index位置的bit設(shè)為true,表示該InternalThreadLocalMap中對該FastThreadLocal已經(jīng)啟動了清理線程
           */

          private BitSet cleanerFlags; 
          private InternalThreadLocalMap() {
                  super(newIndexedVariableTable());
          }

          private static Object[] newIndexedVariableTable() {
                  Object[] array = new Object[32];
                  Arrays.fill(array, UNSET);
                  return array;
          }

          比較簡單,newIndexedVariableTable()方法創(chuàng)建長度為32的數(shù)組,然后初始化為UNSET,然后傳給父類。之后ftl的值就保存到這個數(shù)組里面。

          注意,這里保存的直接是變量值,不是entry,這是和jdk ThreadLocal不同的。InternalThreadLocalMap就先分析到這,其他方法在后面分析ftl再具體說。

          2.3 ftlt的實現(xiàn)分析

          要發(fā)揮ftl的性能優(yōu)勢,必須和ftlt結(jié)合使用,否則就會退化到j(luò)dk的ThreadLocal。ftlt比較簡單,關(guān)鍵代碼如下:

          public class FastThreadLocalThread extends Thread {
            // This will be set to true if we have a chance to wrap the Runnable.
            private final boolean cleanupFastThreadLocals;
            
            private InternalThreadLocalMap threadLocalMap;
            
            public final InternalThreadLocalMap threadLocalMap() {
                  return threadLocalMap;
            }
            public final void setThreadLocalMap(InternalThreadLocalMap threadLocalMap) {
                  this.threadLocalMap = threadLocalMap;
            }
          }  

          ftlt的訣竅就在threadLocalMap屬性,它繼承java Thread,然后聚合了自己的InternalThreadLocalMap。后面訪問ftl變量,對于ftlt線程,都直接從InternalThreadLocalMap獲取變量值。

          2.4 ftl實現(xiàn)分析

          ftl實現(xiàn)分析基于netty-4.1.34版本,特別地聲明了版本,是因為在清除的地方,該版本的源碼已經(jīng)注釋掉了ObjectCleaner的調(diào)用,和之前的版本有所不同。

          2.4.1 ftl的屬性和實例化
          private final int index;

          public FastThreadLocal() {
              index = InternalThreadLocalMap.nextVariableIndex();
          }

          非常簡單,就是給屬性index賦值,賦值的靜態(tài)方法在InternalThreadLocalMap:

           public static int nextVariableIndex() {
                  int index = nextIndex.getAndIncrement();
                  if (index < 0) {
                      nextIndex.decrementAndGet();
                      throw new IllegalStateException("too many thread-local indexed variables");
                  }
                  return index;
            }

          可見,每個ftl實例以步長為1的遞增序列,獲取index值,這保證了InternalThreadLocalMap中數(shù)組的長度不會突增。

          2.4.2 get()方法實現(xiàn)分析
          public final V get() {
              InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); // 1
              Object v = threadLocalMap.indexedVariable(index); // 2
              if (v != InternalThreadLocalMap.UNSET) {
                  return (V) v;
              }

              V value = initialize(threadLocalMap); // 3
              registerCleaner(threadLocalMap);  // 4
              return value;
          }

          1.先來看看InternalThreadLocalMap.get()方法如何獲取threadLocalMap:

          =======================InternalThreadLocalMap=======================  
            public static InternalThreadLocalMap get() {
                  Thread thread = Thread.currentThread();
                  if (thread instanceof FastThreadLocalThread) {
                      return fastGet((FastThreadLocalThread) thread);
                  } else {
                      return slowGet();
                  }
              }
              
            private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
                  InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
                  if (threadLocalMap == null) {
                      thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
                  }
                  return threadLocalMap;
              }    

          因為結(jié)合FastThreadLocalThread使用才能發(fā)揮FastThreadLocal的性能優(yōu)勢,所以主要看fastGet方法。該方法直接從ftlt線程獲取threadLocalMap,還沒有則創(chuàng)建一個InternalThreadLocalMap實例并設(shè)置進去,然后返回。學(xué)習(xí)資料:Java進階視頻資源

          2.threadLocalMap.indexedVariable(index)就簡單了,直接從數(shù)組獲取值,然后返回:

            public Object indexedVariable(int index) {
                  Object[] lookup = indexedVariables;
                  return index < lookup.length? lookup[index] : UNSET;
              }

          3.如果獲取到的值不是UNSET,那么是個有效的值,直接返回。如果是UNSET,則初始化。

          initialize(threadLocalMap)方法:

            private V initialize(InternalThreadLocalMap threadLocalMap) {
                  V v = null;
                  try {
                      v = initialValue();
                  } catch (Exception e) {
                      PlatformDependent.throwException(e);
                  }

                  threadLocalMap.setIndexedVariable(index, v); // 3-1
                  addToVariablesToRemove(threadLocalMap, this); // 3-2
                  return v;
              }

          3.1.獲取ftl的初始值,然后保存到ftl里的數(shù)組,如果數(shù)組長度不夠則擴充數(shù)組長度,然后保存,不展開。

          3.2.addToVariablesToRemove(threadLocalMap, this)的實現(xiàn),是將ftl實例保存在threadLocalMap內(nèi)部數(shù)組第0個元素的Set集合中。

          此處不貼代碼,用圖示如下:

          4.registerCleaner(threadLocalMap)的實現(xiàn),netty-4.1.34版本中的源碼:

          private void registerCleaner(final InternalThreadLocalMap threadLocalMap) {
                  Thread current = Thread.currentThread();
                  if (FastThreadLocalThread.willCleanupFastThreadLocals(current) || threadLocalMap.isCleanerFlagSet(index)) {
                      return;
                  }

                  threadLocalMap.setCleanerFlag(index);

                  // TODO: We need to find a better way to handle this.
                  /*
                  // We will need to ensure we will trigger remove(InternalThreadLocalMap) so everything will be released
                  // and FastThreadLocal.onRemoval(...) will be called.
                  ObjectCleaner.register(current, new Runnable() {
                      @Override
                      public void run() {
                          remove(threadLocalMap);

                          // It's fine to not call InternalThreadLocalMap.remove() here as this will only be triggered once
                          // the Thread is collected by GC. In this case the ThreadLocal will be gone away already.
                      }
                  });
                  */

          }

          由于ObjectCleaner.register這段代碼在該版本已經(jīng)注釋掉,而余下邏輯比較簡單,因此不再做分析。

          2.5 普通線程使用ftl的性能退化

          隨著get()方法分析完畢,set(value)方法原理也呼之欲出,限于篇幅,不再單獨分析。

          前文說過,ftl要結(jié)合ftlt才能最大地發(fā)揮其性能,如果是其他的普通線程,就會退化到j(luò)dk的ThreadLocal的情況,因為普通線程沒有包含InternalThreadLocalMap這樣的數(shù)據(jù)結(jié)構(gòu),接下來我們看如何退化。學(xué)習(xí)資料:Java進階視頻資源

          從InternalThreadLocalMap的get()方法看起:

          =======================InternalThreadLocalMap=======================  
            public static InternalThreadLocalMap get() {
                  Thread thread = Thread.currentThread();
                  if (thread instanceof FastThreadLocalThread) {
                      return fastGet((FastThreadLocalThread) thread);
                  } else {
                      return slowGet();
                  }
              }

            private static InternalThreadLocalMap slowGet() {
                 // 父類的類型為jdk ThreadLocald的靜態(tài)屬性,從該threadLocal獲取InternalThreadLocalMap
                  ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap;
                  InternalThreadLocalMap ret = slowThreadLocalMap.get();
                  if (ret == null) {
                      ret = new InternalThreadLocalMap();
                      slowThreadLocalMap.set(ret);
                  }
                  return ret;
              }

          從ftl看,退化操作的整個流程是:從一個jdk的ThreadLocal變量中獲取InternalThreadLocalMap,然后再從InternalThreadLocalMap獲取指定數(shù)組下標的值,對象關(guān)系示意圖:

          3 ftl的資源回收機制

          在netty中對于ftl提供了三種回收機制:

          自動: 使用ftlt執(zhí)行一個被FastThreadLocalRunnable wrap的Runnable任務(wù),在任務(wù)執(zhí)行完畢后會自動進行ftl的清理。

          手動: ftl和InternalThreadLocalMap都提供了remove方法,在合適的時候用戶可以(有的時候也是必須,例如普通線程的線程池使用ftl)手動進行調(diào)用,進行顯示刪除。

          自動: 為當前線程的每一個ftl注冊一個Cleaner,當線程對象不強可達的時候,該Cleaner線程會將當前線程的當前ftl進行回收。(netty推薦如果可以用其他兩種方式,就不要再用這種方式,因為需要另起線程,耗費資源,而且多線程就會造成一些資源競爭,在netty-4.1.34版本中,已經(jīng)注釋掉了調(diào)用ObjectCleaner的代碼。)

          4 ftl在netty中的使用

          ftl在netty中最重要的使用,就是分配ByteBuf?;咀龇ㄊ牵好總€線程都分配一塊內(nèi)存(PoolArena),當需要分配ByteBuf時,線程先從自己持有的PoolArena分配,如果自己無法分配,再采用全局分配。

          但是由于內(nèi)存資源有限,所以還是會有多個線程持有同一塊PoolArena的情況。不過這種方式已經(jīng)最大限度地減輕了多線程的資源競爭,提高程序效率。

          具體的代碼在PoolByteBufAllocator的內(nèi)部類PoolThreadLocalCache中:

            final class PoolThreadLocalCache extends FastThreadLocal<PoolThreadCache{

              @Override
                  protected synchronized PoolThreadCache initialValue() {
                      final PoolArena<byte[]> heapArena = leastUsedArena(heapArenas);
                      final PoolArena<ByteBuffer> directArena = leastUsedArena(directArenas);

                      Thread current = Thread.currentThread();
                      if (useCacheForAllThreads || current instanceof FastThreadLocalThread) {
                        // PoolThreadCache即為各個線程持有的內(nèi)存塊的封裝  
                        return new PoolThreadCache(
                                  heapArena, directArena, tinyCacheSize, smallCacheSize, normalCacheSize,
                                  DEFAULT_MAX_CACHED_BUFFER_CAPACITY, DEFAULT_CACHE_TRIM_INTERVAL);
                      }
                      // No caching so just use 0 as sizes.
                      return new PoolThreadCache(heapArena, directArena, 00000);
                  }
              }   

          參考資料

          • Netty源碼分析3 - FastThreadLocal 框架的設(shè)計
          • Netty進階:自頂向下解析FastThreadLocal


          轉(zhuǎn)自:Joel.Wang老王

          鏈接:https://blog.csdn.net/mycs2012/article/details/90898128


          最近熱文閱讀:

          1、手寫了一個簡單的JSON解析器,網(wǎng)友直乎:牛!
          2、代碼寫的垃圾被嫌棄?
          3、Redis的這些拓展方案
          4、MySQL的自增 ID 用完了,怎么辦?
          5、多線程環(huán)境下,HashMap為什么會出現(xiàn)死循環(huán)?
          6、10個經(jīng)典場景帶你玩轉(zhuǎn)SQL優(yōu)化
          7、你真的會寫for循環(huán)嗎?來看看這些常見的for循環(huán)優(yōu)化方式
          8、編寫Spring MVC控制器的14個技巧
          9、記一次性能優(yōu)化,單臺4核8G機器支撐5萬QPS
          10、放棄 Hibernate、Mybatis!選擇 JDBCTemplate!
          關(guān)注公眾號,你想要的Java都在這里

          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  人人人人人人摸 | 青青草人人操人人摸人人干 | 午夜国产 码网站 码 | 国产一区二区免费在线观看 | 影音先锋男人站资源 |