相信如果你看到这篇文章数据库连接池你一定不陌生,访问数据库的过程你也一定了然于胸,下面就简单介绍一下。
执行数据库的一系列步骤:
- 通过数据源获取一个数据库连接;
- 创建 Statement;
- 执行 SQL;
- 通过 ResultSet 获取 SQL 执行结果;
- 释放 ResultSet;
- 释放 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);
}
}