springboot采用默认的连接池就是 HikariCP 的连接池,被称为最快的连接池,现在我们看看源码 HikariCP 的源码是怎样的。HikariCP 的连接池中,最核心的代码应该就是 ConcurrentBag、FastList 这个两个数据结构,用于对连接资源的管理,HikariCP 中运用了很多巧妙的设计来提高性能,里面单独某一个功能可能不会起到很大的作用,但是如果合并在一起那么对性能的提高就会很大。现在先来看看 ConcurrentBag 的数据结构
下面是 ConcurrentBag 中定义的所有属性值
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 方法是线程到资源池中借取连接对象,具体的实现是以下
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();
}
}
添加方法的调用是 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();
}
}
归还资源,资源的归还是在 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);
}
}
定义了一个资源的整个状态值的接口
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();
}