02Hikari源码解析之ConcurrentBag、FastList分析
一、介绍
concurrentBag 是一个并发包,其性能优于LinkedBlockingQueue和LinkedTransferQueue。 它尽可能使用ThreadLocal存储来避免锁定,但如果从ThreadLocal列表中没有可用的BagEntry,那就从 公共的集合里面获取。 ThreadLocal 里面的 状态为 Not-in-use 的 连接 也会出现 被“窃取”的情况, 它是一种“无锁”的实现,使用专门的AbstractQueuedLongSynchronizer来管理跨线程信令。
请注意,从袋子中“借来”的BagEntry实际上并未从任何集合中删除,因此即使放弃引用也不会进行垃圾收集。 因此必须通过方法“requite”来返还借用的对象,否则将导致内存泄漏。 只有“删除”方法才能从袋子中完全删除一个对象。
二、ConcurrentBag 分析
2.1 ConcurrentBag属性
//用于线程本地化资源访问
private final ThreadLocal<List<Object>> threadList;
// 存储所有的公用资源
private final CopyOnWriteArrayList<T> sharedList;
// 是否是弱引用
private final boolean weakThreadLocals;
// 用于资源等待线程时的第一手资源交接
private final SynchronousQueue<T> handoffQueue;
下面是 对应的 构造函数:
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));
}
}
threadList :只能在同一个线程里面才可以获取到自己对应的数据,里面的list 是 FastList ,这个 也是对 List进行了一点优化, 详细见下一节
sharedList :是CopyOnWriteArrayList 类型,这样 读数据的时候没有锁,写的时候 加锁了,但是是整体 替换 ,能达到最终一致性.
handoffQueue : 是 SynchronousQueue 类型,SynchronousQueue 是一个无缓存队列
2.2 ConcurrentBag方法
2.2.1 borrow 方法
流程如下:
- 先从threadList 获取状态为STATE_NOT_IN_USE 的 bagEntry ,从后向前获取
- 如果没有获取到,从sharedList 里面获取,首先waiters++
- 如果获取到bagEntry ,判断waiters 是否 >1 ,大于1 说明 还有其他线程也在访问,提醒包裹进行资源添加
- 如果还是 从sharedList 没有获取到bagEntry,那么就从 handoffQueue 里面 进行获取,只要有
线程归还bagEntry ,就可以 在第一时间获取到 - 获取到之后,判断状态是否为STATE_NOT_IN_USE ,如果不是,还可以在timeout 时间内继续poll
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
{
// 先从 threadList里面获取
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;
}
}
//threadList 里面没有获取到时, 从sharedList 里面获取
final int waiting = waiters.incrementAndGet();
try {
for (T bagEntry : sharedList) {
if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
// 同时有多个人在访问,可能“抢走”了其他线程的资源,因此提醒包裹进行资源添加
if (waiting > 1) {
listener.addBagItem(waiting - 1);
}
return bagEntry;
}
}
listener.addBagItem(waiting);
timeout = timeUnit.toNanos(timeout);
// 在规定的时间内,可以多次poll 获取状态为STATE_NOT_IN_USE的
do {
final long start = currentTime();
//从handoffQueue 获取,等待时间为timeout,获取不到返回null
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();
}
}
2.2.2 requite方法
requite 方法会将借来的对象返回到包中。 借来但从未“退回”的物品将导致内存泄漏。
流程如下:
1.先将 bagEntry的状态置为STATE_NOT_IN_USE
2. 判断是否 有waiters 请求线程,如果有,直接转手资源
这里会出现 迭代很久 bagEntry 的状态还是 STATE_NOT_IN_USE,并且队列里面也没有取的需求,这种情况可能时 此bagEntry 在shareList 里面靠后,还没有被迭代到,那就先挂起10毫秒
3.否则进行资源本地化
public void requite(final T bagEntry)
{
//先将状态置为 STATE_NOT_IN_USE
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;
}
// 如果迭代255次,那就说说明虽然很多的线程访问,但是都是在sharedList 获取阶段
// 那就挂起10毫秒
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);
}
}
ConcurrentBag中全部的资源均只能通过add方法进行添加,只能通过remove方法进行移出。
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)) {
yield();
}
}
三、FastList
fastList 也是实现了 List 接口,只是重写实现了 get() 和 remove() 方法 ,将里面的校验rangeCheck(index)去掉了, 能稍微提升一点点点点的 速度吧,就是一行代码
FastList 的get(int index) 方法:
@Override
public T get(int index)
{
return elementData[index];
}
ArrayList 的get(int index) 方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
同理,remove 也是类似
四、总结
这里的borrow 做了一定的优化,用了 CAS + ThreadLocal 尽量避免锁,提升了性能.