十、HikariCP源码分析之ConcurrentBag三
欢迎访问我的博客,同步更新: 枫山别院
源代码版本2.4.5-SNAPSHOT
⑧还回连接
这节我们要分析一下将数据库连接还回到连接池的方法requite
。
请看代码:
/**
* 该方法将借出去的连接还回到连接池中
* 不通过该方法还回的连接会造成内存泄露
*
* @param bagEntry the value to return to the bag
* @throws NullPointerException if value is null
* @throws IllegalStateException if the requited value was not borrowed from the bag
*/
public void requite(final T bagEntry) {
//⑧
//lazySet方法不能保证连接会立刻被设置成未使用状态, 这是个延迟方法
//这是一种优化, 如果要立即生效的话, 可能会需要使用volatile等, 让其他线程立即发现, 这会降低性能, 使用lazySet浪费不了多少时间, 但是不会浪费性能
bagEntry.lazySet(STATE_NOT_IN_USE);
//⑨
//将连接放回到threadLocal中
final List<Object> threadLocalList = threadList.get();
if (threadLocalList != null) {
threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
}
//通知等待线程, 有可用连接
synchronizer.signal();
}
一般我们都是通过 Spring 来使用 HikariCP 的,自己动手启动一个连接池的机会还是少。在 Spring 中使用非常方便,一切都是 Spring 帮我们搞定,我们只管使用,所以需要将连接还回连接池的机会也比较少,也有可能你是间接用过,比如从 HikariCP 中借用的连接,用完之后调用了 close
方法,连接其实并没有真正的被关闭,而是还回了连接池,真正的close
方法被 HikariCP 重写了。其实这是一个至关重要的方法,如果借用出去的连接,不通过这个方法还回来,会导致内存泄露的。
我们来分析下这个方法。
bagEntry.lazySet(STATE_NOT_IN_USE);
这个很眼熟,我们在前面见过compareAndSet
方法,从字面意思理解,这是一个延迟修改状态的方法,lazySet方法不能保证连接会立刻被设置成未使用状态, 这是个延迟方法,因为这是将连接还回去,时效要求并不是那么高,延迟个几十几百毫秒,对用户没有任何影响。反之,如果要立即让状态生效,让其他线程立即能发现的话,那么可能要使用volatile
等,这可能会得不偿失。
⑨放到线程本地threadList
我们前面说过,还回去的连接也会放到线程本地的ThreadLocal
中,方便该线程再次请求连接的时候,可以节省时间,提高性能。
这里的synchronizer.signal();
方法,是通知其他线程有可用的连接加入到连接池了。这里的通知并不是线程间通信的那个通知,只是计数器加 1 了而已,我们在上一节里提过,在循环遍历完连接池没有拿到连接之后,是会检查这个synchronizer
的值,如果比循环之前变大了,就是有可用连接加入到连接池了,这里是其中一个修改synchronizer
的地方,还有创建连接之后,也会将synchronizer
加 1。
⑩添加连接
向连接池中添加一个连接。
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);
synchronizer.signal();
}
这里的bagEntry
是一个连接的包装对象,添加一个的话,就是加入到sharedList
中,而synchronizer.signal();
的作用我们上面刚刚分析过了,就是可用连接的计数器加 1。
⑪移除连接
该方法是从连接池中移除一个连接,是真正的删除。
public boolean remove(final T bagEntry) {
//⑪
//尝试标记移除使用中和保留状态的连接, 如果标记失败, 就是空闲的连接, 直接返回 false
//也就是检查连接的状态, 不能移除空闲的连接或者已经标记移除的连接
if (!bagEntry.compareAndSet(STATE_IN_USE, STATE_REMOVED) && !bagEntry.compareAndSet(STATE_RESERVED, STATE_REMOVED) && !closed) {
LOGGER.warn("Attempt to remove an object from the bag that was not borrowed or reserved: {}", bagEntry);
return false;
}
//如果上面标记成功了, 那么从连接池中移除这个连接
final boolean removed = sharedList.remove(bagEntry);
if (!removed && !closed) {
LOGGER.warn("Attempt to remove an object from the bag that does not exist: {}", bagEntry);
}
// synchronizer.signal();
return removed;
}
这个remove
方法,并不是能移除所有的连接,它只能移除两种状态的连接,分别是STATE_IN_USE
和STATE_RESERVED
。我们看⑪处的代码,如果不是这两个状态,那么会直接打印警告,移除不了。连接有四个状态,除了这两个,还有就是已删除状态,自然不能再次删除了;还有一个就是未使用状态了,也就是说,我们要移除一个未使用状态的连接,那是不行的。
后面就简单了,如果状态修改成功了,那么就从连接池中删除这个连接就可以了,收工!
至此,ConcurrentBag
中重要的方法我们就分析完了,欢迎大家一起讨论。