为什么说HikariCP是性能最好的数据库连接池

相信如果你看到这篇文章数据库连接池你一定不陌生,访问数据库的过程你也一定了然于胸,下面就简单介绍一下。
在这里插入图片描述
执行数据库的一系列步骤:

  1. 通过数据源获取一个数据库连接;
  2. 创建 Statement;
  3. 执行 SQL;
  4. 通过 ResultSet 获取 SQL 执行结果;
  5. 释放 ResultSet;
  6. 释放 Statement; 释放数据库连接。

HiKariCP 的两个优化

FastList (逆序查找数组)

按照规范步骤,执行完数据库操作之后,需要依次关闭 ResultSet、Statement、Connection,由内而外关闭。
当connnection.createStatement创建3个,s1,s2,s3,调用正常数据ArrayList.add就可以添加进数组中,但是当需要关闭时,ArrayListr的remove是顺序移除的,如果想使用ArrayList移除,影响性能。
HiKariCP 中的 FastList 相对于 ArrayList 的一个优化点就是将 remove(Object element) 方法的查找顺序变成了逆序查找,如下图。
在这里插入图片描述

ConcurrentBag(阻塞队列)

如果让我来实现一个数据库连接池,我会用两个阻塞队列来实现,一个存放空闲的连接,一个存放忙碌数据库连接。应为JDK中的阻塞队列是用锁实现的,高并发情况下性能影响很大。
HiKariCP 并没有使用 Java SDK 中的阻塞队列,而是自己实现了一个叫做 ConcurrentBag 的并发容器。
下面是ConcurrentBug的四个属性

//用于存储所有的数据库连接
CopyOnWriteArrayList<T> sharedList;
//线程本地存储中的数据库连接
ThreadLocal<List<Object>> threadList;
//等待数据库连接的线程数
AtomicInteger waiters;
//分配数据库连接的工具,JAVA SDK提供的SynchronousQueue用于线程之间传递数据
SynchronousQueue<T> handoffQueue;

该容器ConcurrentBag的三个方法(下面我们直接用代码来展示三个方法):

add方法

当线程池创建了一个数据库连接时,通过调用add方法添加到ConcurrentBag中

//我们的目的就是将这个连接加入到共享队列 sharedList 中
void add(final T bagEntry){
  //1.加入共享队列
  sharedList.add(bagEntry);
  //2.判断当前如果有等待连接的线程,则通过handoffQueue直接分配给等待的线程
  while (waiters.get() > 0 
    && bagEntry.getState() == STATE_NOT_IN_USE 
    && !handoffQueue.offer(bagEntry)) {
      yield();
  }
}

borrow() 方法

该方法是获得空闲的数据库连接
该方法三个步骤:
1.首先查看线程本地存储是否有空闲连接,如果有,则返回一个空闲的连接;
2.如果线程本地存储中无空闲连接,则从共享队列中获取
3.如果共享队列中也没有空闲的连接,则请求线程需要等待。
需要注意的是,线程本地存储中的连接是可以被其他线程窃取的,所以需要用 CAS 方法防止重复分配。在共享队列中获取空闲连接,也采用了 CAS 方法防止重复分配。

T borrow(long timeout, final TimeUnit timeUnit){
  //1. 先查看线程本地存储是否有空闲连接
  final List<Object> list = threadList.get();
  for (int i = list.size() - 1; i >= 0; i--) {
    final Object entry = list.remove(i);
    final T bagEntry = weakThreadLocals 
      ? ((WeakReference<T>) entry).get() 
      : (T) entry;
    //线程本地存储中的连接也可以被窃取,
    //所以需要用CAS方法防止重复分配
    if (bagEntry != null 
      && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
      return bagEntry;
    }
  }

  // 2.线程本地存储中无空闲连接,则从共享队列中获取
  final int waiting = waiters.incrementAndGet();
  try {
    for (T bagEntry : sharedList) {
      //如果共享队列中有空闲连接,则返回
      if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
        return bagEntry;
      }
    }
    //3.共享队列中没有连接,则需要等待
    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);
    //超时没有获取到连接,返回null
    return null;
  } finally {
    waiters.decrementAndGet();
  }
}

requite() 方法

该方法是用来释放连接的。
步骤:
1.首先将数据库连接状态改为STATE_NOT_IN_USE
2.if存在等待线程获得连接,分配给等待线程
3.else不存在等待线程,将数据库连接保存在线程本地存储中。

//释放连接
void requite(final T bagEntry){
  //1.更新连接状态
  bagEntry.setState(STATE_NOT_IN_USE);
  //2.如果有等待的线程,则直接分配给线程,无需进入任何队列
  for (int i = 0; waiters.get() > 0; i++) {
    if (bagEntry.getState() != STATE_NOT_IN_USE 
      || handoffQueue.offer(bagEntry)) {
        return;
    } else if ((i & 0xff) == 0xff) {
      parkNanos(MICROSECONDS.toNanos(10));
    } else {
      yield();
    }
  }
  //3.如果没有等待的线程,则进入线程本地存储
  final List<Object> threadLocalList = threadList.get();
  if (threadLocalList.size() < 50) {
    threadLocalList.add(weakThreadLocals 
      ? new WeakReference<>(bagEntry) 
      : bagEntry);
  }
}
发布了34 篇原创文章 · 获赞 0 · 访问量 1089

猜你喜欢

转载自blog.csdn.net/qq_42634696/article/details/104669943