HikariCP源码之ConcurrentBag

HikariCP连接池

springboot采用默认的连接池就是 HikariCP 的连接池,被称为最快的连接池,现在我们看看源码 HikariCP 的源码是怎样的。HikariCP 的连接池中,最核心的代码应该就是 ConcurrentBag、FastList 这个两个数据结构,用于对连接资源的管理,HikariCP 中运用了很多巧妙的设计来提高性能,里面单独某一个功能可能不会起到很大的作用,但是如果合并在一起那么对性能的提高就会很大。现在先来看看 ConcurrentBag 的数据结构

ConcurrentBag

下面是 ConcurrentBag 中定义的所有属性值

  • IConcurrentBagEntry:连接资源的接口
  • sharedList:定义共享资源列表,这里面保存了所有的连接资源
  • weakThreadLocals:是否启动弱引用,开启了之后会将连接资源使用 WeakReference 进行包括,在内存不足时进行优先回收
  • threadList:本地资源列表,ConcurrentBag 通过threadList来减少多线程之间的资源竞争,这应该也是 HikariCP 比较快的原因之一吧
  • listener:监听器,用于在获取连接资源不足时,通知连接池创建新的连接资源对象
  • waiters:等待线程,用于记录当前有多少线程在等待资源的获取
  • closed:线程是否关闭
  • handoffQueue:一个阻塞的线程交接队列,使用的是 SynchronousQueue 同步队列,也就是当一个 获取操作时发生,那么必须要有一个存储操作,就是说我队列里面只会存入一条数据,必须获取走了才能进行下一个数据的存储
public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseable
{
   private static final Logger LOGGER = LoggerFactory.getLogger(ConcurrentBag.class);

   /** 保存所有的资源数据 */
   private final CopyOnWriteArrayList<T> sharedList;
   /** 是否启动弱引用 */
   private final boolean weakThreadLocals;
   /** 当前线程中保存连接器资源 */
   private final ThreadLocal<List<Object>> threadList;
   /** 监听器,这里 HikariPool 实现了这个接口 */
   private final IBagStateListener listener;
   /** 当前等待获取连接器的数量 */
   private final AtomicInteger waiters;
   /** 判断当前线程池是否关闭 */
   private volatile boolean closed;
   /** 交接队列,线程池会将连接器对象存储到当前队列中 */
   private final SynchronousQueue<T> handoffQueue;
}

构造函数

构造函数接受了一个 **IBagStateListener ** 监听器,这个监听器的实现类是 HikariPool 这个对象,这个对象里面保存了 ConcurrentBag,也就是说当线程来获取数据时,如果发现池里面没有数据那么就会通知 HikariPool 这个监听器的实现方法创建一个资源,然后存入 ConcurrentBag

public ConcurrentBag(final IBagStateListener listener)
   {
      this.listener = listener;
      //是否使用弱引用,通过 com.zaxxer.hikari.useWeakReferences 配置进行设置
      this.weakThreadLocals = useWeakThreadLocals();
      //创建 SynchronousQueue 交接队列,指定使用公平锁的策略,FIFO先进先出的原则,否则就无序进行获取
      this.handoffQueue = new SynchronousQueue<>(true);
      //初始化等待获取连接器的数量
      this.waiters = new AtomicInteger();
      //设置list,存放所有的资源
      this.sharedList = new CopyOnWriteArrayList<>();
      //是否使用弱引用,如果开启了弱引用直接采用 ArrayList,否则采用 FastList进行数据的存储
      if (weakThreadLocals) {
         this.threadList = ThreadLocal.withInitial(() -> new ArrayList<>(16));
      }
      else {
         this.threadList = ThreadLocal.withInitial(() -> new FastList<>(IConcurrentBagEntry.class, 16));
      }
   }

borrow

borrow 方法是线程到资源池中借取连接对象,具体的实现是以下

  • 先获取到本地线程列表中有没有可以用的资源,先从尾部开始遍历资源,为什么尾部遍历呢?因为HikariCP认为,尾部的资源可以使用的概率比较大,因为归还资源的时候添加数据都是从尾部开始添加,具体的逻辑可以看后面的归还逻辑
  • 将当前等待线程数加1,表示我现在没有获取到资源
  • 遍历共享列表,这个列表是所有线程都可以使用的,这时还需要判断如果当前线程拿了资源,发现后面还有线程在等待,那么通知线程池需要添加资源了
  • 最后一步如果本地资源列表和共享列表里面都没有连接资源了,那么线程会从 **handoffQueue ** 交接队列里面拿资源,并且等待对应的超时时间
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
   {
      // 优先从当前线程中获取连接器资源
      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;
         }
      }

      // 将等待的数量加1
      final int waiting = waiters.incrementAndGet();
      try {
         //从共享资源列表中获取实例对象
         for (T bagEntry : sharedList) {
            //通过CAS修改当前实例资源的状态,判断是否修改成功
            if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
               // 如果大于1,那么说明不止一个线程再等待获取资源(可能偷取了别人的元素),需要通知连接池,需要创建一个连接实例对象
               if (waiting > 1) {
                  listener.addBagItem(waiting - 1);
               }
               //返回连接资源
               return bagEntry;
            }
         }
         //通知连接池,现在需要创建一个连接实例对象,这里listener是 HikariPool
         listener.addBagItem(waiting);
         //设置超时的时间
         timeout = timeUnit.toNanos(timeout);
         do {
            final long start = currentTime();
            //从交接队列中进行数据的获取,
            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 {
         //等待的数量减1
         waiters.decrementAndGet();
      }
   }

add

添加方法的调用是 borrow 方法中监听资源是否足够时通过监听器通知 HikariPool 进行添加的,可以看到 HikariPool.addBagItem() 方法中提交了一个创建器的异步任务 poolEntryCreator.call()

public void addBagItem(final int waiting)
   {
      //传递进来的等待线程的数量减去连接队列数量并且 大于或者等于0
      final boolean shouldAdd = waiting - addConnectionQueueReadOnlyView.size() >= 0; // Yes, >= is intentional.
      if (shouldAdd) {
         addConnectionExecutor.submit(poolEntryCreator);
      }
      else {
         logger.debug("{} - Add connection elided, waiting {}, queue {}", poolName, waiting, addConnectionQueueReadOnlyView.size());
      }
   }


public Boolean call(){
         long sleepBackoff = 250L;
         //判断连接池的状态是否正常以及连接数是否已经达到最大值
         while (poolState == POOL_NORMAL && shouldCreateAnotherConnection()) {
            //创建连接实例
            final PoolEntry poolEntry = createPoolEntry();
            if (poolEntry != null) {
               //将其添加到连接池中
               connectionBag.add(poolEntry);
               logger.debug("{} - Added connection {}", poolName, poolEntry.connection);
               if (loggingPrefix != null) {
                  logPoolState(loggingPrefix);
               }
               return Boolean.TRUE;
            }
            if (loggingPrefix != null) logger.debug("{} - Connection add failed, sleeping with backoff: {}ms", poolName, sleepBackoff);
            quietlySleep(sleepBackoff);
            sleepBackoff = Math.min(SECONDS.toMillis(10), Math.min(connectionTimeout, (long) (sleepBackoff * 1.5)));
         }
         return Boolean.FALSE;
      }

调用 ConcurrentBag.add() 添加资源,资源添加时会将数据整体放到共享资源列表中,然后判断是否有等待的线程数、资源的状态(防止被其它线程获取走了),是否存入到了交接队列里面(线程会等待交接队列);如果后面有线程在等并且资源的状态也是未被使用,但是交接队列没有村进去,那么就会将当前线程的CPU使用权移交出去,让优先级高的线程先执行。

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)) {
         Thread.yield();
      }
   }

requite

归还资源,资源的归还是在 ProxyConnection.close() 方法中执行的,这里先不看。

  • 资源归还时先将状态值设置为未使用
  • 然后判断是否有线程在等待,有等待的线程直接放入交接队列中
  • 如果没有线程等待,那么就直接存入到本地资源列表里面
 public void requite(final T bagEntry)
   {
      //先将资源的状态设置为未使用
      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;
         }
         else if ((i & 0xff) == 0xff) { //如果当前遍历的数为255还有线程在等待获取,那么就休眠10微秒
            parkNanos(MICROSECONDS.toNanos(10));
         }
         else {
            //否则就通知cpu让出当前线程的执行权,等其它的高优先级的线程执行完毕后再执行
            Thread.yield();
         }
      }
      //如果没有正在获取的线程,那么直接将资源放入当前线程的资源列表中
      final List<Object> threadLocalList = threadList.get();
      //如果当前线程的资源列表超过了50个了,就不需要当前实例资源了,等待销毁即可
      if (threadLocalList.size() < 50) {
         //将资源添加到本地线程的资源列表中
         threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
      }
   }

IConcurrentBagEntry

定义了一个资源的整个状态值的接口

public interface IConcurrentBagEntry
   {
      //没有被使用
      int STATE_NOT_IN_USE = 0;
      //被使用
      int STATE_IN_USE = 1;
      //移除
      int STATE_REMOVED = -1;
      //保留
      int STATE_RESERVED = -2;


      /**
       *
       *
       * @param expectState 期望状态
       * @param newState    新状态
       * @return boolean
       * @date 2023-08-03 09:32:38
       */
      boolean compareAndSet(int expectState, int newState);
      void setState(int newState);
      int getState();
   }

你可能感兴趣的:(java)