1. 背景

之前读JUC的AQS源码,读到Condition部分,我当时也写了一篇源码阅读文章--(AbstractQueuedSynchronizer源码解读--续篇之Condition)[http://www.cnblogs.com/micrari/p/7219751.html]。Doug Lea大师的代码写的很好,整个设计与编码都很优秀。但是我也在最后的思考与总结中指出了Condition有一个缺陷,在于await/awaitNanos/awaitUntil那些方法,在JavaDoc中写了应该要在持有互斥锁的情况下调用,否则通常会抛出IllegalMonitorStateException。确实在AQS的Condition的实现中会抛出异常,但异常是在fullyRelease->release->tryRelease这样的一条方法调用链中被抛出的。而在fullyRelease之前,会做一件事情就是addConditionWaiter。

这显然是有问题的,ConditionObject中的firstWaiter/lastWaiter都是没有被volatile修饰的,它们的可见性是通过锁的获取和释放来保证的。如果有线程因为某些情况实际上没有持有互斥锁,但是调用了ConditionObject的await,尽管可能会因为fullyRelease方法的调用发现未持有互斥锁而抛出IllegalMonitorStateException,但此时可能已经对ConditionObject的内部数据结果造成了永久性破坏。比如可能有些其他正常通过持有锁来await的线程,再也不能被唤醒了。

2. 提bug

不得不说,这个bug是看源码看出的bug。JDK或者我们日常不会遇到的原因是因为我们一直在正确地使用Condition。保证await前要先持锁,保证signal前也要先持锁。但是作为JDK,必须保证库的鲁棒性,也就是在意外情况下同样能够处理,并且不会崩。

AQS的Condition和JVM提供的wait/notify的native实现,效果是很类似的。作为对比使用wait/notify。即便在有线程未持锁的情况下调用wait,会抛出IllegalMonitorStateException,但是原先wait的线程仍然最终可以被唤醒。但是AQS的Condition由于上述逻辑上的一些疏忽,会导致错误的调用抛出期望的异常,但对内部数据结构造成破坏,导致不可靠。

于是,上了Oracle的bugs.java.com。给Oracle提了一个bug,流程不复杂也不简单,还是要填不少bug相关信息,并且要给出测试用例。我写的用例是这样的:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class ConditionObjectTest { private static volatile int count = 0; public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Condition cond = lock.newCondition(); int loop = 100;
for (int i = 0; i < loop; i++) {
new Thread(() -> {
lock.lock();
try {
count++;
cond.await();
} catch (InterruptedException ignore) {
} finally {
lock.unlock();
count--;
}
}, "succ-" + i).start(); new Thread(() -> {
boolean illegalMonitor = false;
try {
cond.awaitUninterruptibly();
} catch (IllegalMonitorStateException ignore) {
illegalMonitor = true;
} finally {
assert illegalMonitor;
}
}, "fail-" + i).start(); } while (count != loop) {
Thread.yield();
} while (count > 0) {
lock.lock();
try {
cond.signalAll();
} finally {
lock.unlock();
}
}
}
}

其实代码很简单,要做的事情就是证明如果在有线程不持有锁调用Condition#await的情况下,最终这些非法调用都会抛出异常。但是那些正常await的线程,却会出现有的怎么也没办法被唤醒,整个程序hang住。

提完之后,Oracle的bus.java.com会显示已经提交到内部了,等内部审核通过后会分配一个bugID。过了没几天我收到邮件称bug被审核过了,分配的ID是JDK-8187408

3.结果

接下来20多天,Oracle内部人员貌似对我提的这个bug讨论还挺多的。刚开始有人认为这不是bug,Java Doc写了需要持有锁的情况下调用await。我觉得作为JDK,代码实现不能停留在通过Doc来约束的程度啊,作为库来说鲁棒性非常重要,应该能hold住错误的请求并且还屹立不倒。而且我认为这个bug改起来很容易,和signal一样,在方法一开始就先去检查是否持有锁就行了。

还有人称不明白为什么测试用例要打开断言。其实我的测试用例里的断言只是为了证明那些非法请求确实都被抛出异常了。

不过后面人觉得确实是bug,还有个哥们称他发现这个bug貌似早就被引入了。然后翻出了Doug Lea老爷子很早以前有一次改代码的记录,参考链接





这算是上古时期AQS的代码了,可以看到Doug Lea老爷子当时把checkConditionAccess方法改为了isHeldExclusively。checkConditionAccess原来是会在每个ConditionObject方法(await/signal那些)都被调用的,但是isHeldExclusively却不会在await方法中调用,await方法通过release来判断锁。

所以这就是这个bug的根源,从逻辑上来说确实release里面也包含了判断是否持有互斥锁的逻辑,但实现语义以及鲁棒性却因为这个改动被弱化了很多。

Doug Lea居然改了这个bug

没想到Doug Lea老爷子最后改了这个bug。

我觉得Doug Lea老爷子这明显还是改的复杂了,这个条件有那么复杂么。不过这个代码确实很Doug Lea(一个if里干了一大堆的事情)。

不过最终JSR166小组还是简化了if



和我自己预期的修复方法一样,不就这么一句话就搞定的事情么。

另外他们也修了一下isHeldExclusively的JavaDoc注释,指出isHeldExclusively会被所有ConditionObject方法调用到。

最后,他们还修了一下AQS的使用样例,参考链接,这里就不贴图了。

最终,此bug的修复被合并到JDK10b26中。



JDK10原来Oracle已经开始在启动开发了呀。我连Java9都不会玩。

给JDK提的一个bug(关于AbstractQueuedSynchronizer.ConditionObject)的更多相关文章

  1. glassfish 一个bug重现

        原文链接:https://java.net/jira/browse/GLASSFISH-21293?jql=project%20%3D%20GLASSFISH%20AND%20resoluti ...

  2. Tomcat一个BUG造成CLOSE_WAIT

    之前应该提过,我们线上架构整体重新架设了,应用层面使用的是Spring Boot,前段日子因为一些第三方的原因,略有些匆忙的提前开始线上的内测了.然后运维发现了个问题,服务器的HTTPS端口有大量的C ...

  3. MySQL关于exists的一个bug

    今天碰到一个很奇怪的问题,关于exists的, 第一个语句如下: SELECT ) FROM APPLY t WHERE EXISTS ( SELECT r.APPLY_ID FROM RECORD ...

  4. 微软BI 之SSIS 系列 - MVP 们也不解的 Scrip Task 脚本任务中的一个 Bug

    开篇介绍 前些天自己在整理 SSIS 2012 资料的时候发现了一个功能设计上的疑似Bug,在 Script Task 中是可以给只读列表中的变量赋值.我记得以前在 2008 的版本中为了弄明白这个配 ...

  5. [android开发IDE]adt-bundle-windows-x86的一个bug:无法解析.rs文件--------rs_core.rsh file not found

    google的android自带的apps写的是相当牛逼的,将其导入到eclipse中方便我们学习扩展.可惜关于导入的资料太少了,尤其是4.1之后的gallery和camera合二为一了.之前导4.0 ...

  6. 记录一个使用HttpClient过程中的一个bug

    最近用HttpClient进行链接请求,开了多线程之后发现经常有线程hang住,查看线程dump java.lang.Thread.State: RUNNABLE at java.net.Socket ...

  7. 应用服务器中对JDK的epoll空转bug的处理

    原文链接:应用服务器中对JDK的epoll空转bug的处理 前面讲到了epoll的一些机制,与select和poll等传统古老的IO多路复用机制的一些区别,这些区别实质可以总结为一句话, 就是epol ...

  8. 由一个bug引发的SQLite缓存一致性探索

    问题 我们在生产环境中使用SQLite时中发现建表报“table xxx already exists”错误,但DB文件中并没有该表.后面才发现这个是SQLite在实现过程中的一个bug,而这个bug ...

  9. Win10系统菜单打不开问题的解决,难道是Win10的一个Bug ?

    Win10左下角菜单打不开,好痛苦,点击右下角的时间也没反应,各种不爽,折磨了我好几天,重装又不忍心,实在费劲,一堆开发环境要安装,上网找了很多方法都不适用.今天偶然解决了,仔细想了下,难道是Win1 ...

随机推荐

  1. 梦想还是要有的-纪念正式成为csdn博客专家暨年中总结

    csdn博客:http://blog.csdn.net/tuzongxun 我的csdn历程(坚持总会有收获):   一年零三个月之前,2015年3月3日,我在csdn写下第一篇技术博客,只是记录了一 ...

  2. QC11客户端安装

    win10使用hp qc11 步骤1:安装vcredist_x86,32位 步骤2:安装浏览器客户端 ALMExplorerAddIn,11版本 可能遇到的问题 1. 出现Initialization ...

  3. HMM隐马尔科夫模型

    这是一个非常重要的模型,凡是学统计学.机器学习.数据挖掘的人都应该彻底搞懂. python包: hmmlearn 0.2.0 https://github.com/hmmlearn/hmmlearn ...

  4. python矩阵运算 不断收集整理

    python矩阵运算 转自:http://blog.sina.com.cn/s/blog_5f234d4701012p64.html Python使用NumPy包完成了对N-维数组的快速便捷操作.使用 ...

  5. 关于MongoDb Replica Set的故障转移集群——理论篇

    自从10 gen用Replica Set取代Master/Slave方案后生活其实已经容易多了,但是真正实施起来还是会发现各种各样的小问题,如果不小心一样会栽跟头. 在跟Replica Set血拼几天 ...

  6. swift 关于 toolbar 学习笔记

    import UIKit class ViewController: UIViewController { @IBOutlet weak var toolBar: UIToolbar! @IBOutl ...

  7. C JAVA你可能不知道的那些编程细节

    关于printf的格式化字符 %* *与其它占位符结合使用, *将首先被一个 int 变量值代替后再被格式化. 如 printf("%.*s.", 2, "Hello&q ...

  8. IDApython教程(一)

    IDAPython是IDA的一个功能强大的扩展特性,对外提供了大量的IDA API调用.另外,还能在使用python 脚本语言的过程中获得能力提升,所以我强烈推荐所有的逆向工程师使用它. 然而不幸的是 ...

  9. SpringBoot------个性化启动Banner设置

    1.添加Banner.txt文件 . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ ...

  10. 【Linux】命令写在文件中并调用awk -f

    我们在使用awk命令的时候,有时候命令特别长,在终端写出来格式太乱,难以阅读,以下是一个将命令写在文件中,并使用awk调用的具体案例 1.现在有文件file3.txt,内容如下: 2.ak2.awk脚 ...