【HikariCP】【ConcurrentBag】源码学习

Hikari目前已经是springboot的默认数据库连接池,并且以高效和轻量著称,因为代码量比较少,所以可以阅读一下,学习一下,github地址:HikariCP

ConcurrentBag

这个是HikariCP的核心功能类,作者在注释中也详细说明了这个类的作用

/**
 * This is a specialized concurrent bag that achieves superior performance
 * to LinkedBlockingQueue and LinkedTransferQueue for the purposes of a
 * connection pool.  It uses ThreadLocal storage when possible to avoid
 * locks, but resorts to scanning a common collection if there are no
 * available items in the ThreadLocal list.  Not-in-use items in the
 * ThreadLocal lists can be "stolen" when the borrowing thread has none
 * of its own.  It is a "lock-less" implementation using a specialized
 * AbstractQueuedLongSynchronizer to manage cross-thread signaling.
 *
 * Note that items that are "borrowed" from the bag are not actually
 * removed from any collection, so garbage collection will not occur
 * even if the reference is abandoned.  Thus care must be taken to
 * "requite" borrowed objects otherwise a memory leak will result.  Only
 * the "remove" method can completely remove an object from the bag.
 *
 * @author Brett Wooldridge
 *
 * @param <T> the templated type to store in the bag
 */
public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseable

翻译能力有限,借助谷歌翻译,大致意思如下:

这是一个专门的并发包,可以为连接池实现LinkedBlockingQueue和LinkedTransferQueue的卓越性能。 它尽可能使用ThreadLocal存储来避免锁定,但如果ThreadLocal列表中没有可用的项目,则会使用它来扫描公共集合。 当借用线程没有自己的东西时,ThreadLocal列表中的未使用项可能被“窃取”。 它是一个“无锁”实现,使用专门的AbstractQueuedLongSynchronizer来管理跨线程信令。

请注意,从包中“借用”的项目实际上并未从任何集合中删除,因此即使放弃引用也不会发生垃圾收集。 因此,必须小心“重新”“借用”借来的对象,否则将导致内存泄漏。 只有“删除”方法才能从包中完全删除对象。

我对这个类的理解就是在多线程的并发场景中,通过自己封装的类,在保证线程安全的情况下维护连接池的安全和高效,数据结构比LinkedBlockingQueue和LinkedTransferQueue这两个queue要高效。

成员变量

  • private final CopyOnWriteArrayList sharedList;

这个成员变量首先要注意到的是它是CopyOnWriteArrayList类型的,这个类型是Java并发包中的类,主要就是解决ArrayList在多线程读写场景中会出现的ConcurrentModificationException。ArrayList在内部维护了一个modCount修改次数的成员变量,如果在写的过程中多个线程同时进行修改,那么当前的修改次数和维护的修改次数可能会不一致导致抛出了这个异常。

所以为了避免这个情况,CopyOnWriteArrayList这个类是这么处理的:读是没有影响的,但是如果要往集合中写元素,那么首先会复制出一个当前List的copy,然后在这个copy中添加这个元素,最后把原来List的地址引用指向这个copy。如果在复制的过程中有线程去读取,那么它读到的是原来的集合中的值。这里因为涉及到集合的复制,所以这种一般是用于集合元素比较稳定,基本不会发生写入的情况。如果元素一直在变化,那么在大数据量的情况下复制的开销是很大的。具体的详情可以推荐这个博客查看CopyOnWriteArrayList的原理和使用方法

回到这个成员变量这个是共享元素的集合。这个元素必须是IConcurrentBagEntry的实例,在hikari中就是封装了数据库连接的分装类,也就是连接

  • private final boolean weakThreadLocals;

是否是弱引用ThreadLocal

  • private final ThreadLocal<List> threadList;

线程持有的成员变量是一个List的借用元素集合

  • private final IBagStateListener listener;

包状态监听器接口

  • private final AtomicInteger waiters;

原子类,等待者数量

  • private volatile boolean closed;

volatile类型,并发包是否关闭

  • private final SynchronousQueue handoffQueue;

这个SynchronousQueue是Java并发包中的,大致的作用是它是一个只有容量1的队列,当生产者生产完放入元素后就会阻塞,直到消费者将该元素取出消费。这样做保证了消息的一对一传递。网上随便一搜有很多这个类的说明。

内部接口

  • IConcurrentBagEntry
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();
}

共享元素需要实现的就是这个接口,这个接口定义了四种状态,还有设置状态、获取状态和设置的状态和想要的状态是否一致三个方法

  • IBagStateListener
public interface IBagStateListener
{
  void addBagItem(int waiting);
}

这个监听器接口只有一个方法是增加元素

构造方法

public ConcurrentBag(final IBagStateListener listener)
{
  this.listener = listener;
  this.weakThreadLocals = useWeakThreadLocals();

  this.handoffQueue = new SynchronousQueue<>(true);
  this.waiters = new AtomicInteger();
  this.sharedList = new CopyOnWriteArrayList<>();
  if (weakThreadLocals) {
     this.threadList = ThreadLocal.withInitial(() -> new ArrayList<>(16));
  }
  else {
     this.threadList = ThreadLocal.withInitial(() -> new FastList<>(IConcurrentBagEntry.class, 16));
  }
}

构造器传入一个监听器,然后赋值到成员变量上。weakThreadLocals的赋值是调用方法:

private boolean useWeakThreadLocals()
{
  try {
     if (System.getProperty("com.zaxxer.hikari.useWeakReferences") != null) {   // undocumented manual override of WeakReference behavior
        return Boolean.getBoolean("com.zaxxer.hikari.useWeakReferences");
     }

     return getClass().getClassLoader() != ClassLoader.getSystemClassLoader();
  }
  catch (SecurityException se) {
     return true;
  }
}

这个方法中可以看到首先是使用者配置的时候时候配置了这个选项,如果没有配置,那么检查当前类的类加载器是不是自己定义的类加载器,如果是自己定义的,那么就是弱引用

然后是handoffQueue、waiters和sharedList的实例化。最后的threadList实例化的时候需要检查是否是弱引用,如果是弱引用,那么实例化的是一个16容量的ArrayList,如果不是,那么用的是作者自己开发的FastList。

方法

  • public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
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();
  }
}

方法调用传入的两个参数,一个是等待时间的长度,一个是等待时间的单位,如果想要“借”的时间超时,就直接返回null。首先在threadlocal的list从后往前遍历,通过list.remove()方法取出连接后,检查是不是弱引用,如果是弱引用就强转成弱引用。然后检查这个连接是不是空,或者状态是不是未使用状态,如果是的,将该连接的状态改成正在使用,并且返回连接。

如果在threadlocal的list中没有找到,那么先把等待者的计数器+1,然后sharedList进行遍历查找连接,如果里面有未使用的连接,那么将这个连接状态改成正在使用,然后将等待者的计数器-1。

如果还是得不到连接,那么开始循环,直到时间小于10秒钟。循环中一直从handoffQueue中获取连接。

  • public void requite(final T bagEntry)
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();
  if (threadLocalList.size() < 50) {
     threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
  }
}

归还连接,传入参数的就是连接。首先将连接的状态改成未使用,然后遍历等待者,如果存在需要使用连接的等待者,那么直接放到handoffQueue中给借用者使用。如果没有,将该线程阻塞10ms。最后是如果当前线程的连接数少于50条,那么将这个线程加入到该线程的threadlocal中

  • public void add(final T bagEntry)
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);

  // spin until a thread takes it or none are waiting
  while (waiters.get() > 0 && bagEntry.getState() == STATE_NOT_IN_USE && !handoffQueue.offer(bagEntry)) {
     yield();
  }
}

添加连接方法,首先要检查是不是已经关闭了,如果被关闭了就不能添加。如果没有关闭,可以在sharedList中增加此连接。如果有消费者在等待并且该连接的状态是未使用并且handoffQueue中不能添加这个连接,那么这个添加线程先休息。

  • public boolean remove(final T bagEntry)
public boolean remove(final T bagEntry)
{
  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);
  }

  return removed;
}

移除连接方法,如果这个连接的状态不能从正在使用状态改成移除状态,并且不能从保留状态改成移除状态,并且没有关闭,那么移除失败。然后尝试从sharedList移除这个连接,最后返回list.remove()方法的结果

  • public void close()
@Override
public void close()
{
  closed = true;
}

关闭方法就是将closed的值改成true

  • public List values(final int state)
public List<T> values(final int state)
{
  final List<T> list = sharedList.stream().filter(e -> e.getState() == state).collect(Collectors.toList());
  Collections.reverse(list);
  return list;
}

找出sharedList中指定状态的所有连接。

  • public List values()
public List<T> values()
{
  return (List<T>) sharedList.clone();
}

没有指定状态,那么将sharedList的复制返回

  • public boolean reserve(final T bagEntry)
public boolean reserve(final T bagEntry)
{
  return bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_RESERVED);
}

这个方法是将连接的状态从未使用改成保留,这样就不会被借出去。这个方法主要用于使用values(int)方法,保留的连接可以被删除。

  • public void unreserve(final T bagEntry)
public void unreserve(final T bagEntry)
{
  if (bagEntry.compareAndSet(STATE_RESERVED, STATE_NOT_IN_USE)) {
     // spin until a thread takes it or none are waiting
     while (waiters.get() > 0 && !handoffQueue.offer(bagEntry)) {
        yield();
     }
  }
  else {
     LOGGER.warn("Attempt to relinquish an object to the bag that was not reserved: {}", bagEntry);
  }
}

将连接的状态从保留改成未使用,这样就变得可用,可以让别的线程“借用”

  • public int getWaitingThreadCount()
public int getWaitingThreadCount()
{
  return waiters.get();
}

获取从等待到借用到连接的花费时间

  • public int getCount(final int state)
public int getCount(final int state)
{
  int count = 0;
  for (IConcurrentBagEntry e : sharedList) {
     if (e.getState() == state) {
        count++;
     }
  }
  return count;
}

统计这次调用时指定状态的连接数

  • public int[] getStateCounts()
public int[] getStateCounts()
{
  final int[] states = new int[6];
  for (IConcurrentBagEntry e : sharedList) {
     ++states[e.getState()];
  }
  states[4] = sharedList.size();
  states[5] = waiters.get();

  return states;
}

不明所以的方法,没有使用的方法,不知道为什么没有删除

  • public int size()
public int size()
{
  return sharedList.size();
}

获取共享连接数

  • public void dumpState()
public void dumpState()
{
  sharedList.forEach(entry -> LOGGER.info(entry.toString()));
}

输出共享连接集合中的所有连接到日志

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值