续接上集如何设计一款高性能的数据库连接工具?过来看这里,这是一款号称拥有“光”一样链接速度的工具,我们看它是怎样实现的(第二节:Hikari是如何实现的底层逻辑)-优快云博客
1.ConcurrentBag(连接池存储容器)
今天分享Hikari中的一个最核心的类,ConcurrentBag,它是实现了连接池高性能的一个最重要的原因,这个类用来存放最终的PoolEntry(原生链接的包装)类型的连接对象,提供了基本的增删查的功能,被HikariPool持有,上面那么多的操作,几乎都是在HikariPool中完成的,HikariPool用来管理实际的连接生产动作和回收动作,实际操作的是ConcurrentBag类。
2.官方的解释
这是一个专门的并发包,在连接池方面实现了优于LinkedBlockingQueue和LinkedTransferQueue的性能。它尽可能使用ThreadLocal存储来避免锁,但如果ThreadLocal列表中没有可用项,则会扫描公共集合。当借用线程没有自己的线程时,ThreadLocal列表中未使用的项目可能会被“窃取”。这是一个“无锁”实现,使用专门的AbstractQueuedLongSynchronizer来管理跨线程信令。请注意,从包中“借用”的项目实际上并没有从任何收集中删除,因此即使放弃引用,也不会发生垃圾收集。因此,必须注意“归还”借用的对象,否则将导致内存泄漏。只有“移除”方法才能从Bag里完全移除对象。
3.状态转换
IConcurrentBagEntity简单理解为Hikari对JdbcConnection的一层封装,有四种状态
public interface IConcurrentBagEntry
{
int STATE_NOT_IN_USE = 0;
int STATE_IN_USE = 1;
int STATE_REMOVED = -1;
int STATE_RESERVED = -2;
boolean compareAndSet(int expectState, int newState);
void setState(int newState);
int getState();
}
从代码里面可以看到,连接池对象,会有4个状态转换分别是:NOT_IN_USE、IN_USE、REMOVED、RESERVED,他们之间的转换逻辑如下图:
4.ConcurrentBag的结构
ConcurrentBag的结构如下图,重点关注
- sharedList和threadList,是两个关于PoolEntry(JdbcConnection的封装)的列表,用来实际存储连接池中维护的Jdbc连接
- listener,是指向HikariPool的引用,当ConcurrentBag获取连接时发现用完了,则通过listener的回调接口请求HikariPool多扩充点Jdbc连接入池
- handoffQueue,和listener相关,HikariPool扩充连接池后将新的连接通过该队列提供给正在等待的线程,正在
getConnection
的线程会在queue的另一端阻塞住,等待扩充连接后喂给它们

5.成员变量
private final CopyOnWriteArrayList<T> sharedList;
private final boolean weakThreadLocals;
private final ThreadLocal<List<Object>> threadList;
private final IBagStateListener listener;
private final AtomicInteger waiters;
private volatile boolean closed;
private final SynchronousQueue<T> handoffQueue;
ConcurrentBag 整体就是无锁设计,有三个重要的成员变量:
- ThreadLocal 缓存,加快本地连接获取速度
- CopyOnWriteArrayList,写时拷贝List
- SynchronousQueue,无存储的等待队列
6.执行逻辑
获取数据库连接基本流程如下:
- 当取连接的时候会先去 ThreadLocal 去找以前用过的连接,如果找到连接状态是可以使用的话拿直接返回。(ThreadLocal 是本地资源,每个线程都优先去自己本地去找,所以竞争也更少,需要遍历的连接也更少,所以速度就更快)
- 找不到再去 sharedList 这个共享的写时复制列表中查找可用连接。
- 如果再找不到,则通过 handoffQueue 等待可用的连接,如果超过一定时间则返回 null。
7.最后回顾一下
ConcurrentBag 的优化思路就是本地缓存有的去本地缓存找连接,找不到就去公共的 sharedList 去找,还找不到就等着。
通过将连接本地存储化来减少竞争,又根据连接池读多写少的特性用 CopyOnWriteArrayList 来实现 sharedList 。
当然还有像上面 borrow 和 requite 的一些细节也值得品味,追求极致速度就需要扣细节。