小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
前言
- 目前SpringBoot默认的数据库连接池是Hikari,相对于Druid连接池来说:
- Hikari的特点就是快,其内部运用了很多优化机制和操作,主要就是为了更高的性能。
- 而Druid连接池特点不是快,是对数据和sql的监控分析。
- 两者各有各的特点,没有哪个是最好,根据项目和需求选择适合的连接池。
FastList
- Hikari连接池内部为了有更好的性能,放弃了使用ArrayList集合,而是自定义了一个FastList集合
- 其内部和ArrayList实现相似,主要在get方法和remove方法有了不同的调整优化
- 上图里圈起来的部分是FastList对获取元素方法的优化,去除了对下标索引的验证。
- 上图中指出的位置是FastList对删除元素方法的优化,ArrayList是从头开始扫描元素进行删除,FastList是从尾部开始扫描元素进行删除
- 在Hikari里一般是会删除(关闭)最近一个放入的元素,所以FastList会避免一部分的无效查找。
ConcurrentBag
- Hikari连接池内部除了自定义了FastList集合外,还自定义了ConcurrentBag并发访问集合
- ConcurrentBag是可以多线程并发访问,但看其内部源码会发现没有锁定资源操作
- 支持并发操作主要是其内部的三个属性:threadList、sharedList、handoffQueue
- threadList:保存当前线程的本地链接资源
- sharedList:保存所有的链接资源
- handoffQueue:阻塞式一进一出队列
/**
* The method will borrow a BagEntry from the bag, blocking for the
* specified timeout if none are available.
*
* @param timeout how long to wait before giving up, in units of unit
* @param timeUnit a <code>TimeUnit</code> determining how to interpret the timeout parameter
* @return a borrowed instance from the bag or null if a timeout occurs
* @throws InterruptedException if interrupted while waiting
*/
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
{
// Try the thread-local list first
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;
}
}
// Otherwise, scan the shared list ... then poll the handoff queue
final int waiting = waiters.incrementAndGet();
try {
for (T bagEntry : sharedList) {
if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
// If we may have stolen another waiter's connection, request another bag add.
if (waiting > 1) {
listener.addBagItem(waiting - 1);
}
return bagEntry;
}
}
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 {
waiters.decrementAndGet();
}
}
复制代码
- borrow()获取链接资源方法执行流程:
- 首先在threadList对象获取当前线程保存的链接资源,若获取成功,就设置状态为STATE_IN_USE并返回链接资源;
- threadList里获取链接资源失败,就在sharedList集合里获取空闲连接资源,若获取成功,就设置状态为STATE_IN_USE并返回链接资源;
- sharedList里获取链接资源失败,就在handoffQueue队列里等空闲连接资源进入,超时返回;
注:sharedList返回的链接资源后,其还是存在sharedList集合内部,其他线程也能访问这个链接资源,只是其状态为STATE_IN_USE,不能被使用。
注:sharedList对象是使用CopyOnWriteArrayList集合,符合当前Hikari连接池里是读多写少场景(链接资源数固定,并复用资源)。
复制代码
/**
* This method will return a borrowed object to the bag. Objects
* that are borrowed from the bag but never "requited" will result
* in a memory leak.
*
* @param bagEntry the value to return to the bag
* @throws NullPointerException if value is null
* @throws IllegalStateException if the bagEntry was not borrowed from the bag
*/
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) {
parkNanos(MICROSECONDS.toNanos(10));
}
else {
yield();
}
}
final List<Object> threadLocalList = threadList.get();
threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
}
复制代码
- requite()放回链接资源方法执行流程:
- 首先把当前链接资源对象的状态设置为STATE_NOT_IN_USE,
- 然后判断当前链接资源是否已经被其他线程使用,若没有再把当前链接资源放入队列唤醒阻塞的线程,循环256次后睡眠10纳秒;
- 最后把链接资源对象放入本地线程资源内;
最后
- Hikari连接池使用ConcurrentBag集合并发无锁化机制和FastList集合提高性能
- 当然在Hikari里的快不只是因为这两个集合,还有很多其他的优化到极致的点
- 参考:这波性能优化,太炸裂了!
- 虚心学习,共同进步 -_-
近期评论