读懂HikariCP一百行代码,多线程就是个孙子!

总结:java届很难得有读百十行代码就能增加修炼的机会,这里有一个。
通常,我在看书的时候一般不写代码,因为我的脑袋被设定成单线程的,一旦同时喂给它不同的信息,它就无法处理。
但多线程对电脑来说就是小菜一碟,它可以同时做很多事,看起来匪夷所思。好希望把自己的大脑皮层移植到这些牛x的设备上。
用人脑思考电脑正在思考的问题,这本身就是一种折磨。但平常的工作和面试中,又不得不面对这样的场景,所以多线程就成了编程路上一块难啃的骨头。
hikaricp是springboot默认的数据库连接池,它毫不谦虚的的起了一个叫做光的名字,这让国产druid很没面子。
还是言归正传,看一下hikari中的concurrentbag吧。
核心数据结构 多线程代码一个让人比较头疼的问题,就是每个api我都懂,但就是不会用。很多对concurrent包倒背如流的同学,在面对现实的问题时,到最后依然不得不被迫加上lock或者synchronized。
concurrentbag是一个lock free的数据结构,主要用作数据库连接的存储,可以说整个hikaricp的核心就是它。删掉乱七八糟的注释和异常处理,可以说关键的代码也就百十来行,但里面的道道却非常的多。
concurrentbag速度很快,要达到这个目标,就需要一定的核心数据结构支持。
private final copyonwritearraylist sharedlist;private final threadlocal threadlist;private final atomicinteger waiters;private final synchronousqueue handoffqueue; sharedlist 用来缓存所有的连接,是一个copyonwritearraylist结构。 threadlist 用来缓存某个线程所使用的所有连接,相当于快速引用,是一个threadlocal类型的arraylist。 waiters 当前正在获取连接的等待者数量。atomicinteger,就是一个自增对象。当waiters的数量大于0时候,意味着有线程正在获取资源。 handoffqueue 0容量的快速传递队列,synchronousqueue类型的队列,非常有用。 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();} 有了这些数据结构的支持,我们的concurrentbag就可以实现它光的宣称了。
基于 spring boot + mybatis plus + vue & element 实现的后台管理系统 + 用户小程序,支持 rbac 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/yunaiv/ruoyi-vue-pro 视频教程:https://doc.iocoder.cn/video/ 获取连接 连接的获取是borrow方法,还可以传入一个timeout作为超时控制。
public t borrow(long timeout, final timeunit timeunit) throws interruptedexception 首先,如果某个线程执行非常快,使用了比较多的连接,就可以使用threadlocal的方式快速获取连接对象,而不用跑到大池子里面去获取。代码如下。
// try the thread-local list firstfinal var list = threadlist.get();for (int i = list.size() - 1; i >= 0; i--) {    final var entry = list.remove(i);    final t bagentry = weakthreadlocals ? ((weakreference) entry).get() : (t) entry;    if (bagentry != null && bagentry.compareandset(state_not_in_use, state_in_use)) {        return bagentry;    }} 我们都知道,包括arraylist和hashmap一些基础的结构,都是fail fast的,如果你在遍历的时候,删掉一些数据,有可能会引起问题。幸运的是,由于我们的list是从threadlocal获取的,它首先就避免了线程安全的问题。
接下来就是遍历。这段代码采用的是尾遍历(头遍历会出现错误),用于快速的从列表中找到一个可以复用的对象,然后使用cas来把状态置为使用中。但如果对象正在被使用,则直接删除它。
在concurrentbag里,每个threadlocal最多缓存50个连接对象引用。
当threadlocal里找不到可复用的对象,它就会到大池子里去拿。也就是下面这段代码。
// otherwise, scan the shared list ... then poll the handoff queuefinal 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 var 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();} 首先要注意,这段代码可能是由不同的线程执行的,所以必须要考虑线程安全问题。由于shardlist是线程安全的copyonwritearraylist,适合读多写少的场景,我们可以直接进行遍历。
这段代码的目的是一样的,需要从sharedlist找到一个空闲的连接对象。这里把自增的waiting变量传递到外面的代码进行处理,主要是由于想要根据waiting的大小来确定是否创建新的对象。
如果无法从池子里获取连接,则需要等待别的线程释放一些资源。
创建对象的过程是异步的,要想获取它,还需要依赖一段循环代码。while循环代码是纳秒精度,会尝试从handoffqueue里获取。最终会调用synchronousqueue的transfer方法。
基于 spring cloud alibaba + gateway + nacos + rocketmq + vue & element 实现的后台管理系统 + 用户小程序,支持 rbac 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/yunaiv/yudao-cloud 视频教程:https://doc.iocoder.cn/video/ 归还连接 有借就有还,当某个连接使用完毕,它将被归还到池子中。
public void requite(final t bagentry){   bagentry.setstate(state_not_in_use);   for (var 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 {         thread.yield();      }   }   final var threadlocallist = threadlist.get();   if (threadlocallist.size() < 50) {      threadlocallist.add(weakthreadlocals ? new weakreference(bagentry) : bagentry);   }} 首先,把这个对象置为可用状态。然后,代码会进入一个循环,等待使用方把这个连接接手过去。当连接处于state_not_in_use状态,或者队列中的数据被取走了,那么就可以直接返回了。
由于waiters.get()是实时获取的,有可能长时间一直大于0,这样代码就会变成死循环,浪费cpu。代码会尝试不同层次的睡眠,一个是每隔255个waiter睡10ns,一个是使用yield让出cpu时间片。
如果归还连接的时候并没有被其他线程获取到,那么最后我们会把归还的连接放入到相对应的threadlocal里,因为对一个连接来说,借和还,通常是一个线程。
知识点 看起来平平无奇的几行代码,为什么搞懂了就能hold住大部分的并发编程场景呢?主要还是这里面的知识点太多。下面我简单罗列一下,你可以逐个攻破。
使用threadlocal来缓存本地资源引用,使用线程封闭的资源来减少锁的冲突 采用读多写少的线程安全的copyonwritearraylist来缓存所有对象,几乎不影响读取效率 使用基于cas的atomicinteger来计算等待者的数量,无锁操作使得计算更加快速 0容量的交换队列synchronousqueue,使得对象传递更加迅速 采用compareandset的cas原语来控制状态的变更,安全且效率高。很多核心代码都是这么设计的 在循环中使用park、yield等方法,避免死循环占用大量cpu 需要了解并发数据结构中的offer、poll、peek、put、take、add、remove方法的区别,并灵活应用 cas在设置状态时,采用了volatile关键字修饰,对于volatile的使用也是一个常见的优化点 需要了解weakreference弱引用在垃圾回收时候的表现 麻雀虽小,五脏俱全。如果你想要你的多线程编程能力更上一层楼,读一读这个短小精悍的concurrentbag吧。当你掌握了它,多线程的那些东西,不过是小菜一碟。


香烟铝箔纸的巧用
Verilog HDL锁存器实现
英特尔酷睿i5和i7处理器该如何选择
是什么原因造成电感器漏磁的呢
物联网安全性的重要性
读懂HikariCP一百行代码,多线程就是个孙子!
防爆电机运行条件_防爆电机运行注意事项
射频前端芯片是高性能天线调谐开关芯片的企业之一
PLC中较常用的协议
MarshallTufton体验 多出了一个硬核便携的标签
iPhone8什么时候上市?iphone 8发布日期渐进,消息汇总:苹果8或将放弃指纹解锁,首批缺货涨价成必须
上海汽车借助软件模型开发出混合动力控制器
美国国防部反对商务部限制美国企业供货华为 华为开发者大会2020因疫情延期至3月
阿里达摩院实现低成本激光雷达,自动驾驶技术再升级
心电信号检测中滤除肌电干扰的方法解析
一款适合在小体积产品应用的BS45F3832雾化器MCU介绍
电脑行业可以使用精密光纤激光打标机吗
2017年最值得买的4款手机:红米Note4X、360N5、华为荣耀V9、华为P10,性价比爆棚!
盘点几款音质出色的蓝牙耳机,好音质更享受
索尼公司宣布推出新版机器人狗Aibo