十、HikariCP源码分析之ConcurrentBag三

2022-06-29  本文已影响0人  枫山别院

欢迎访问我的博客,同步更新: 枫山别院

源代码版本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_USESTATE_RESERVED。我们看⑪处的代码,如果不是这两个状态,那么会直接打印警告,移除不了。连接有四个状态,除了这两个,还有就是已删除状态,自然不能再次删除了;还有一个就是未使用状态了,也就是说,我们要移除一个未使用状态的连接,那是不行的。

后面就简单了,如果状态修改成功了,那么就从连接池中删除这个连接就可以了,收工!

至此,ConcurrentBag中重要的方法我们就分析完了,欢迎大家一起讨论。

上一篇下一篇

猜你喜欢

热点阅读