前言

ConcurrentBag是HikariCP中实现的一个池化资源的并发管理类。它是一个高性能的生产者-消费者队列。

ConcurrentBag的并发性能优于LinkedBlockingQueue和LinkedTransferQueue

LinkedBlockingQueue 阻塞队列

LinkedTransferQueue 数据传送队列

TransferQueue继承自BlockingQueue接口 TransferQueue的改进:

保留与完成

保留是指消费者线程在消费时如果发现队列为空,就生成一个空元素入队,然后该消费者线程在这个资源的数据字段上旋转等待。

完成是当生产者线程要放入一个新资源时,如果发现首位元素的数据字段为空,就把数据直接填充到这个元素中。

保留加完成,共同称为数据的传送。

为了提升并发效率,ConcurrentBag

  1. 优先使用ThreadLocal里的资源,如果ThreadLocal的List里没有可用的资源了,再使用公共集合(资源池)里的资源。
  2. 无论ThreadLocal还是公共集合,都使用CAS代替加锁

IConcurrentBagEntry接口

ConcurrentBag中定义了一个public的成员接口IConcurrentBagEntry,并作为这个类的泛型,要求所有要接受ConcurrentBag管理的池化资源都要实现这个接口

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

两个重要的方法

1. 借用池化资源

public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
  • 参数:

    • timeout: 超时时长
    • timeunit: 时长单位

首先尝试请求ThreadLocal的资源

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;
}
}

ThreadLocal里没有请求到资源,就去请求公共集合里的资源

final int waiting = waiters.incrementAndGet(); // waiters是个Atomic Integer,表示等待的消费者数量
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);
do {
final long start = currentTime();
final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
// 返回null表示超时
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. 归还借来的资源

public void requite(final T bagEntry)
  • 参数

    • bagEntry: 要归还的资源

如果有线程正在等待,尝试将资源归还到handoffQueue,使用定期parkNanos和yield防止当前操作占用了过多的CPU资源

for (int i = 0; waiters.get() > 0; i++) {
if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
return;
}
else if ((i & 0xff) == 0xff) { // 每尝试256次,就阻塞10ms
parkNanos(MICROSECONDS.toNanos(10));
}
else {
Thread.yield(); // 让出CPU调度
}
}

如果没有线程在等待,把资源归还到当前线程的ThreadLocal,因为同一次操作里很可能多次获取连接,要提高一次操作的效率。

可以看出,与常规的生产者-消费者模型不同,每次借用完一定要归还,因为borrow操作中没有删除资源的动作,GC是不可能去回收资源的,不及时归还的话可能导致内存泄露。

综合以上代码,对并发编程有以下启发:

  1. 高并发场景下,尽量避免使用synchronized这种重量级的锁,而是用Atomic、CopyOnWrite、CAS等轻量级的方式保证并发安全。
  2. 不能让一个线程长时间占用资源,要适当地给其它线程让行。
  3. 要尽量用低消耗的操作替代高消耗的操作,如这里的尽量不创建新资源。
  4. 常用的资源尽量放到ThreadLocal中。

读HikariCP源码学Java(一)-- 通过ConcurrentBag类学习并发编程思想的更多相关文章

  1. 读HikariCP源码学Java(二)—— 因地制宜的改装版ArrayList:FastList

    前言 如前文所述,HikariCP为了提高性能不遗余力,其中一个比较特别的优化是它没有直接使用ArrayList,而是自己实现了FastList,因地制宜,让数组的读写性能都有了一定程度的提高. 构造 ...

  2. 如何阅读Java源码 阅读java的真实体会

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心.   说到技术基础,我打个比 ...

  3. Java源码阅读的真实体会(一种学习思路)

    Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈 ...

  4. Java源码阅读的真实体会(一种学习思路)【转】

    Java源码阅读的真实体会(一种学习思路)   刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+ ...

  5. 读jQuery源码之整体框架分析

    读一个开源框架,大家最想学到的就是设计的思想和实现的技巧.最近读jQuery源码,记下我对大师作品的理解和心得,跟大家分享,权当抛砖引玉. 先附上jQuery的代码结构. (function(){ / ...

  6. [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作

    [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作 目录 [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作 0x00 摘要 0x01 业务领域 1.1 SOFARegis ...

  7. Android菜鸟的成长笔记(6)——剖析源码学自定义主题Theme

    原文:Android菜鸟的成长笔记(6)--剖析源码学自定义主题Theme 还记得在Android菜鸟的成长笔记(3)中我们曾经遇到了一个问题吗?"这个界面和真真的QQ界面还有点不同的就是上 ...

  8. [从源码学设计]蚂蚁金服SOFARegistry之程序基本架构

    [从源码学设计]蚂蚁金服SOFARegistry之程序基本架构 0x00 摘要 之前我们通过三篇文章初步分析了 MetaServer 的基本架构,MetaServer 这三篇文章为我们接下来的工作做了 ...

  9. [从源码学设计]蚂蚁金服SOFARegistry之消息总线

    [从源码学设计]蚂蚁金服SOFARegistry之消息总线 目录 [从源码学设计]蚂蚁金服SOFARegistry之消息总线 0x00 摘要 0x01 相关概念 1.1 事件驱动模型 1.1.1 概念 ...

随机推荐

  1. 依赖反转原则DIP 与使用了Repository模式的asp.net core项目结构

    DIP 依赖反转原则 Dependency Inversion Principle 的定义如下: 高级别的模块不应该依赖于低级别的模块, 他们都应该依赖于抽象. 假设Controller依赖于Repo ...

  2. DB性能瓶颈分析思路

    在性能分析过程中,经常遇到性能瓶颈出现在SQL的情况,此类问题通常可以分为两大类场景,一是SQL自身性能差导致的慢,如索引缺失.索引失效.统计信息不准确.SQL过于复杂等:二是由于外部原因等待导致的S ...

  3. 工具 | Typora + PicGo-Core 自动上传图片到图床

    0 前言 Markdown 是现在十分流行的标记式语言,在博客等很多场景中应用十分广泛.众所周知,Markdown 中的图片是以链接的形式存在的,不像 Word 等传统文本编辑器直接把图片嵌入文档中. ...

  4. 设计Web页面(2)

    1.前面我们新建了一个空白的ASP.NET网页,那么接下来这章我们就讲一下设计Web页面 2.布局页面有两种方法,一种是通过Table表格来布局页面窗体,另一种是通过CSS+DIV来布局窗体,其中作为 ...

  5. 一文彻底掌握Apache Hudi的主键和分区配置

    1. 介绍 Hudi中的每个记录都由HoodieKey唯一标识,HoodieKey由记录键和记录所属的分区路径组成.基于此设计Hudi可以将更新和删除快速应用于指定记录.Hudi使用分区路径字段对数据 ...

  6. BUAA_OO_第一单元

    BUAA_OO_2020_UNIT1 一.程序结构分析 第一次作业 UML & Mertrics ​ 由于数据处理简单,第一次作业中笔者发挥了面向过程的思想,将项转换成Biginteger, ...

  7. OO UNIT 2 个人总结

    第二单元面向对象作业--性感电梯在线吃人 Part 1:单部可捎带电梯 多线程设计策略 本次电梯仅仅只有一部运行,因此,在多线程的设计中难度不大,并且,只需采用一对一的生产者-消费者模型即可解决问题. ...

  8. Spring Boot自动配置原理

    使用Spring Boot之后,一个整合了SpringMVC的WEB工程开发,变的无比简单,那些繁杂的配置都消失不见了,这 是如何做到的? 一切魔力的开始,都是从我们的main函数来的,所以我们再次来 ...

  9. fiddler 手机抓包(含https) 完整流程

    第一部分:下载并安装fiddler 一.使用任一浏览器搜索[fiddler下载安装],并下载fiddler 安装包. 二.fiddler安装包下载成功后,将下载的fiddler压缩包解压到自定义文件夹 ...

  10. Day16_94_IO_循环读取文件字节流read()方法(二)

    循环读取文件字节流read()方法 通过read()循环读取数据,但是read()每次都只能读取一个字节,频繁读取磁盘对磁盘有伤害,且效率低. import java.io.FileInputStre ...