02Hikari源碼解析之ConcurrentBag、FastList分析

一、介紹

concurrentBag 是一個併發包,其性能優於LinkedBlockingQueue和LinkedTransferQueue。 它儘可能使用ThreadLocal存儲來避免鎖定,但如果從ThreadLocal列表中沒有可用的BagEntry,那就從 公共的集合裏面獲取。 ThreadLocal 裏面的 狀態爲 Not-in-use 的 連接 也會出現 被“竊取”的情況, 它是一種“無鎖”的實現,使用專門的AbstractQueuedLongSynchronizer來管理跨線程信令。

請注意,從袋子中“借來”的BagEntry實際上並未從任何集合中刪除,因此即使放棄引用也不會進行垃圾收集。 因此必須通過方法“requite”來返還借用的對象,否則將導致內存泄漏。 只有“刪除”方法才能從袋子中完全刪除一個對象。

二、ConcurrentBag 分析

2.1 ConcurrentBag屬性

 //用於線程本地化資源訪問
   private final ThreadLocal<List<Object>> threadList;
   // 存儲所有的公用資源
   private final CopyOnWriteArrayList<T> sharedList;
   // 是否是弱引用
   private final boolean weakThreadLocals;
  // 用於資源等待線程時的第一手資源交接
   private final SynchronousQueue<T> handoffQueue;

下面是 對應的 構造函數:

public ConcurrentBag(final IBagStateListener listener)
   {
      this.listener = listener;
      this.weakThreadLocals = useWeakThreadLocals();

      this.handoffQueue = new SynchronousQueue<>(true);
      this.waiters = new AtomicInteger();
      this.sharedList = new CopyOnWriteArrayList<>();
      if (weakThreadLocals) {
         this.threadList = ThreadLocal.withInitial(() -> new ArrayList<>(16));
      }
      else {
         this.threadList = ThreadLocal.withInitial(() -> new FastList<>(IConcurrentBagEntry.class, 16));
      }
   }

threadList :只能在同一個線程裏面纔可以獲取到自己對應的數據,裏面的list 是 FastList ,這個 也是對 List進行了一點優化, 詳細見下一節

sharedList :是CopyOnWriteArrayList 類型,這樣 讀數據的時候沒有鎖,寫的時候 加鎖了,但是是整體 替換 ,能達到最終一致性.

handoffQueue : 是 SynchronousQueue 類型,SynchronousQueue 是一個無緩存隊列

2.2 ConcurrentBag方法

2.2.1 borrow 方法

流程如下:

  1. 先從threadList 獲取狀態爲STATE_NOT_IN_USE 的 bagEntry ,從後向前獲取
  2. 如果沒有獲取到,從sharedList 裏面獲取,首先waiters++
  3. 如果獲取到bagEntry ,判斷waiters 是否 >1 ,大於1 說明 還有其他線程也在訪問,提醒包裹進行資源添加
  4. 如果還是 從sharedList 沒有獲取到bagEntry,那麼就從 handoffQueue 裏面 進行獲取,只要有
    線程歸還bagEntry ,就可以 在第一時間獲取到
  5. 獲取到之後,判斷狀態是否爲STATE_NOT_IN_USE ,如果不是,還可以在timeout 時間內繼續poll
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
   {
      // 先從 threadList裏面獲取
      final List<Object> list = threadList.get();
      for (int i = list.size() - 1; i >= 0; i--) {
         final Object entry = list.remove(i);
         @SuppressWarnings("unchecked")
         final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;
         if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
            return bagEntry;
         }
      }
     //threadList 裏面沒有獲取到時, 從sharedList 裏面獲取
      final int waiting = waiters.incrementAndGet();
      try {
         for (T bagEntry : sharedList) {
            if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
              // 同時有多個人在訪問,可能“搶走”了其他線程的資源,因此提醒包裹進行資源添加
               if (waiting > 1) {
                  listener.addBagItem(waiting - 1);
               }
               return bagEntry;
            }
         }

         listener.addBagItem(waiting);

         timeout = timeUnit.toNanos(timeout);
         
          // 在規定的時間內,可以多次poll 獲取狀態爲STATE_NOT_IN_USE的
         do {
            final long start = currentTime();
             //從handoffQueue 獲取,等待時間爲timeout,獲取不到返回null
            final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
            if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
               return bagEntry;
            }

            timeout -= elapsedNanos(start);
         } while (timeout > 10_000);

         return null;
      }
      finally {
         waiters.decrementAndGet();
      }
   }

2.2.2 requite方法

requite 方法會將借來的對象返回到包中。 借來但從未“退回”的物品將導致內存泄漏。
流程如下:
1.先將 bagEntry的狀態置爲STATE_NOT_IN_USE
2. 判斷是否 有waiters 請求線程,如果有,直接轉手資源
這裏會出現 迭代很久 bagEntry 的狀態還是 STATE_NOT_IN_USE,並且隊列裏面也沒有取的需求,這種情況可能時 此bagEntry 在shareList 裏面靠後,還沒有被迭代到,那就先掛起10毫秒
3.否則進行資源本地化

public void requite(final T bagEntry)
   {
      //先將狀態置爲 STATE_NOT_IN_USE
      bagEntry.setState(STATE_NOT_IN_USE);

     // 判斷是否存在等待線程,若存在,則直接轉手資源
      for (int i = 0; waiters.get() > 0; i++) {
         if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
            return;
         }
         // 如果迭代255次,那就說說明雖然很多的線程訪問,但是都是在sharedList 獲取階段
         // 那就掛起10毫秒 
         else if ((i & 0xff) == 0xff) {
            parkNanos(MICROSECONDS.toNanos(10));
         }
         else {
            yield();
         }
      }
        // 否則,進行資源本地化
      final List<Object> threadLocalList = threadList.get();
      if (threadLocalList.size() < 50) {
         threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
      }
   }

ConcurrentBag中全部的資源均只能通過add方法進行添加,只能通過remove方法進行移出。

public void add(final T bagEntry)
   {
      if (closed) {
         LOGGER.info("ConcurrentBag has been closed, ignoring add()");
         throw new IllegalStateException("ConcurrentBag has been closed, ignoring add()");
      }

      sharedList.add(bagEntry);

     //旋轉直到有線程取走它或沒有任何線程等待
      while (waiters.get() > 0 && bagEntry.getState() == STATE_NOT_IN_USE && !handoffQueue.offer(bagEntry)) {
         yield();
      }
   }

三、FastList

fastList 也是實現了 List 接口,只是重寫實現了 get() 和 remove() 方法 ,將裏面的校驗rangeCheck(index)去掉了, 能稍微提升一點點點點的 速度吧,就是一行代碼

FastList 的get(int index) 方法:

 @Override
   public T get(int index)
   {
      return elementData[index];
   }

ArrayList 的get(int index) 方法

public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }

同理,remove 也是類似

四、總結

這裏的borrow 做了一定的優化,用了 CAS + ThreadLocal 儘量避免鎖,提升了性能.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章