经过仔细检查后确认了这是个bug,存在于JDK1.7.0_40和刚发布的JDK8中,去google和oracle官方似乎也没有搜索到这个问题。

重现bug:先来重现下这个bug,由于对并发线程的执行顺序预先不能做任何假设,所以很可能根本就不存在所谓的重现错误的“测试用例”,或者说这个测试用例应该是某种“执行顺序"。所以我一开始的做法是copy了一份ltq的源码,通过某个地方加自旋...但是这种方法毕竟要修改源码,后来我发现直接debug进源码就可以轻易重现bug了。

LinkedTransferQueue:xfer(E e, boolean haveData, int how, long nanos)if (how != NOW) { // No matches availableif (s == null)s = new Node(e, haveData);Node pred = tryAppend(s, haveData);if (pred == null)continue retry; // lost race vs opposite modeif (how != ASYNC)return awaitMatch(s, pred, e, (how == TIMED), nanos);}return e; // not waiting

在以上06行Node pred = tryAppend(s, havaData)  断点(我是windows下用eclipse调试);
debug以下代码:

public static void main(String[] args) {final BlockingQueue<Long> queue = new LinkedTransferQueue<Long>();Runnable offerTask = new Runnable(){public void run(){queue.offer(8L);System.out.println("offerTask thread has gone!");}};Runnable takeTask = new Runnable(){public void run(){try {System.out.println(Thread.currentThread().getId() + " " +queue.take());} catch (InterruptedException e) {e.printStackTrace();}}};Runnable takeTaskInterrupted = new Runnable(){public void run(){Thread.currentThread().interrupt();try {System.out.println(Thread.currentThread().getId() + " " +queue.take());} catch (InterruptedException e) {System.out.println(e + " "+Thread.currentThread().getId());}}};new Thread(offerTask).start();new Thread(takeTask).start();new Thread(takeTaskInterrupted).start();
}

执行到断点处之后,在Debug界面里面有Thread-0、Thread-1、Thread-2三个线程分别指代代码中的offerTask、takeTask、takeTaskInterrupted三者。现在执行三步:

step 1: Resume Thread-1(没有输出,线程Thread-1自己挂起,等待数据)
step 2: Resume Thread-2(看到类似于 java.lang.InterruptedException 15 的输出)
step 3: Resume Thread-0(输出:offerTask thread has gone!)

offer线程已经执行完毕,然后我们的8呢,明明Thread-1在等待数据,数据丢失了吗?其实不是,只不过take线程现在无法取得offer线程提交的数据了。

如果你觉得上面的数据丢失还不是什么大问题请在上面的示例下添加如下代码(和你CPU核心数相同的代码行:)

        ..............new Thread(takeTask).start();new Thread(takeTask).start();new Thread(takeTask).start();new Thread(takeTask).start();

把上面的3个step重新按顺序执行一遍,建议先打开任务管理器,接着忽略断点,让接下来这几个线程跑:)
CPU爆满了吧...其实是被这几个线程占据了,你去掉几行代码,CPU使用率会有相应的调整。
所以这个bug可能会引起数据暂时遗失和CPU爆满, 只不过貌似发生这种情况的概率极低。

原因:为什么会出现这个bug呢,要想了解原因必须先深入分析ltq内部所使用的数据结构和并发策略,ltq内部采用的是一种非常不同的队列,即松弛型双重队列(Dual Queues with Slack)。

数据结构:

松弛的意思是说,它的head和tail节点相较于其他并发列队要求上更放松,构造它的目的是减少CAS操作的次数(相应的会增加next域的引用次数),举个例子:某个瞬间tail指向的节点后面已经有6个节点了(以下图借用源码的注释-_-|||没画过图),而其他并发队列真正的尾节点最多只能是tail的下一个节点。

* head                      tail
* |                               |
* v                             v
* M -> M -> U -> U -> U -> U->U->U->U->U

收缩的方式是大部分情况下不会用tail的next来设置tail节点,而是第一次收缩N个next(N>=2),然后查看能否2个一次来收缩tail。(head类似,并且head改变一次会导致前“head"节点的next域断裂即如下图)

*"prehead"                head                 tail
*     |                              |                      |
*     v                            v                      v
*    M      M-> U -> U -> U -> U->U->U->U->U

双重是指有两种类型相互对立的节点(Node.isData==false || true),并且我理解的每种节点都有三种状态:

1  INIT(节点构造完成,刚进入队列的状态)

2 MATCHED(节点备置为“满足”状态,即入队节点标识的线程成功取得或者传递了数据)

3 CANCELED(节点被置为取消状态,即入队节点标识的线程因为超时或者中断决定放弃等待)

(bug的原因就是现有代码中将2、3都当做MATCHED处理,后面会看到把3独立出来就修复了这个问题)

并发策略:

既然使用了松弛的双重队列,那么当take、offer等方法被调用时执行的策略也稍微不同。

就我们示例中的代码的流程来看,Thread-0、Thread-1、Thread-2几乎同时进入到了xfer的调用,发现队列为空,所以都构造了自己的node希望入队,于是三者都从tail开始加入自己的node,我们在这里的顺序是Thread-1->Thread-2->Thread-0,因为想要入队还要和当前的tail节点进行匹配得到“认可”才能尝试入队,队列为空Thread-1理所当然入队成功并且挂起了自己的线程(park)等待相对的调用来唤醒自己(unpark),然后Thread-2发现队列末尾的node和自己是同一类型的,于是通过了测试把自己也加入了队列,由于本身是中断的所以让自己进入MATCHED状态(bug就是这里了,上面说过CANCEL被当做MATCHED状态处理),接着我们提交数据的Thread-0来了,发现末尾节点的类型虽然对立但却是MATCHED状态(假如不为MATCHED会有退回再从head来探测一次的机会),所以认为队列已经为空,前面的调用已经被匹配完了,然后把自己的node入队,这样就形成了如下所示的场景:

*                                            Thread-1         Thread-2          Thread-0
*                                                 |                        |                         |
*                                                 v                       v                        v
*                                       REQUEST ->     MATCHED   ->         DATA

好了, 现在Thread-3来了,先探测尾部发现Thread-0的node是类型相反的,于是退回从头部开始重新探测,但是又发现Thread-1的node的类型是相同的,于是再次去探测尾部看看能否入队.......结果造成CPU是停不下来的。

修复:

如上面所说,错误的本质在于当尾部的节点是CANCELED(取消)状态时不能作为被匹配完成的MATCHED状态处理,应该让后来者回退到head去重新测试一次所以重点是对源码做出如下修改(修改放在注释中):

static final class Node {
final boolean isData; // false if this is a request node
volatile Object item; // initially non-null if isData; CASed to match
volatile Node next;
volatile Thread waiter; // null until waiting/*static final Object CANCEL = new Object();*/

在Node节点代码中加入标识取消的对象CANCEL。

private E xfer(E e, boolean haveData, int how, long nanos) {if (item != p && (item != null) == isData  /*&& item!=Node.CANCEL*/) { // unmatched
if (isData == haveData) // can't match

在xfer函数中添加对于为状态为取消的判断。

private E xfer(E e, boolean haveData, int how, long nanos) {Node pred = tryAppend(s, haveData);.....}private Node tryAppend(Node s, boolean haveData) {else if (p.cannotPrecede(/*s, */haveData))else {
/* if(p.isCanceled())
p.forgetContents();*/
if (p != t) { // update if slack now >= 2

添加对于前置节点为取消状态时当前节点的入队策略

final boolean cannotPrecede(boolean haveData) {
boolean d = isData;
Object x;
return d != haveData && (x = item) != this && (x != null) == d;}final boolean cannotPrecede(Node node, boolean haveData) {
boolean d = isData;
if(d != haveData){
Object x = item;
if(x != this && (x!=null) == d && x!= Node.CANCEL)
return true;
if(item == CANCEL){
if(node.next != this){
node.next = this;
return true;
}
this.forgetContents();
}
}
node.next = null;
return false;}

这一步是关键, 当我们入队时发现前置节点是类型对立并且取消状态时,我们就需要多一次的回退探测,所以借用了一下next域来标识这个CANCEL节点,下次经过时或者可以确认它可以当做MATCHED处理(它前面没有INIT节点)或者已经有别的节点粘接在它后面,我们就进而处理那个节点,总之当我们总是能够得到正确的行为。

private E awaitMatch(Node s, Node pred, E e, boolean timed, long nanos) {if ((w.isInterrupted() || (timed && nanos <= 0)) &&
s.casItem(e, /*Node.CANCEL*/)) { // cancel
unsplice(pred, s);
return e;}

这一处关键点把item的值从原来的s本身修改为我们新增的CANCEL.

代码有点乱,关于这个bug定位应该没问题,后面的原因很多方面都没讲,剩下的还有很多处大大小小的修改,bug已经提交到bugs.java.com,整个修改之后的LinkedTransferQueue在github上,已经通过了  JSR166测试套件。

重点的代码改动如下图:

 

Bug:LinkedTransferQueue的数据暂失和CPU爆满相关推荐

  1. java中splicebug,Bug:LinkedTransferQueue的数据暂失和CPU爆满以及修复

    一个因中断或者超时的调用可能会引起数据丢失和CPU爆满. 前几天读LinkedTransferQueue(以下简称ltq)的源码,想加深下对松弛型双重队列的理解,无意中发现了这个问题:),经过仔细检查 ...

  2. mc服务器cpu占用过高怎么办,我的世界崩服无报错 传送导致CPU爆满

    我的世界崩服无报错 传送导致CPU爆满.服务器如果崩服报错了,这其实并不是很可怕的,最可怕的是崩服了米有任何报错数据产生,这是服主们应该时刻注意的.据统计,不少的服主会发现地图中有玩家传送就会导致CP ...

  3. 阿里云虚拟主机针对恶意频繁攻击式访问造成CPU爆满的解决方法

    阿里云虚拟主机针对恶意频繁攻击式访问造成CPU爆满的解决方法 参考文章: (1)阿里云虚拟主机针对恶意频繁攻击式访问造成CPU爆满的解决方法 (2)https://www.cnblogs.com/xi ...

  4. STM32使用串口1配合DMA接收不定长数据,减轻CPU载荷

    STM32使用串口1配合DMA接收不定长数据,减轻CPU载荷 http://www.openedv.com/thread-63849-1-1.html 实现思路:采 用STM32F103的串口1,并配 ...

  5. mysql服务器cpu爆满解决办法

    碰到一个mysql cpu爆满的问题,简单记录下 步骤: 1.通过show processlist找到耗时最长的 mysql> show processlist; +----+------+-- ...

  6. 透过数据看国产CPU性能排行

    透过数据看国产CPU性能排行 国产CPU发展至今,可谓百花齐放,各家技术发展路线与产品各不相同.近日,一组关于主流桌面CPU的性能横评公布于网上,具体数据可能因为个体差异和测试环境因素有差别,但展现出 ...

  7. 线上cpu爆满dump文件分析 精确查到根本产生原因

    建议使用如下工具:JProfiler 然后安装并打开,将dump文件放入其中 然后选中点击 然后你会看到此次cpu爆满根本的原因

  8. 服务器(Linux)挖矿木马病毒(kswapd0进程使cpu爆满)

    服务器(Linux)挖矿木马病毒(kswapd0进程使cpu爆满) 前言:由于本人的阿里云服务器遭受攻击,被挖矿,导致CUP爆满,同时受到阿里云官方的邮箱.短信以及电话通知(监管部门是不允许服务器被直 ...

  9. JVM:线上服务CPU爆满,如何排查(三)

    0. 引言 前一段时间出现了一个正则表达式引起的线上CPU爆满的问题,一开始没有在第一时间定位到问题,这里也特此记录一下,同时也系统的梳理下CPU爆满问题的排查思路和方法,为后续的同学提供参考. 1. ...

最新文章

  1. 数据库服务器跟网站服务器间传输慢的问题
  2. Flutter开发之路由跳转与传参(七)
  3. 003_Jsp动作标签
  4. 全球计算机科学硕士申请,2019爱尔兰留学都柏林大学计算机科学硕士申请
  5. AMBA总线协议AHB、APB
  6. 共享几套silverlight2 toolkit最新的皮肤控件样式下载
  7. leetcode538. 把二叉搜索树转换为累加树
  8. 数据可视化组件Grafana详细解读--在Docker上安装Grafana管理平台
  9. python多任务_【python】多任务(1. 线程)
  10. linux调整网页视频声音,网络学员面试常见问题请你修改一下LINUX的视频驱动和声音.DOC...
  11. 接收list参数_Python 犄角旮旯--List
  12. 如何进行文件管理、备份
  13. sublime Text 2使用小技巧
  14. python 画ks曲线_风控模型—区分度评估指标(KS)深入理解应用
  15. 渥太华大学计算机硕士课程,渥太华大学留学生经验分享:攻克语言关最简单的方法就是少用中文...
  16. 镁光闪存颗粒对照表_最全的内存颗粒编码规则说明,教你看穿内存条到底用的什么颗粒...
  17. 树莓派Pico与ADXL345三轴加速度传感器SPI总线接口技术及MicroPython编程
  18. android与iPhoneX区别,iphone8plus和iphonex区别 买iphone8plus还是iphonex好
  19. h61 nvme硬盘_移动硬盘怎么选?看完这篇不踩坑
  20. 【钉钉-场景化能力包】企业做培训直播

热门文章

  1. 「镁客·请讲」关于VR行业内容发展破局之道,我们和水晶石做了一番走心的交流...
  2. 视频编解码的步骤和主要技术
  3. Oracle数据库的启动过程和关闭模式
  4. ZooKeeper 倪超著 观后感
  5. wps下载 wps怎么显示所有窗口?
  6. span标签限制显示的字数长度
  7. 全国计算机能力挑战赛练习题
  8. [SQLite 开发] 查询去掉重复数据
  9. EasyExcel自定义表头导出模板并封装数据下拉选择
  10. Iterated Logarithm Function 多重对数函数