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 尽量避免锁,提升了性能.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直打铁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值