给JDK提的一个bug(关于AbstractQueuedSynchronizer.ConditionObject)
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)的更多相关文章
- 一个bug肝一周...忍不住提了issue
导航 Socket.IO是什么 Socket.IO的应用场景 为什么选socket.io-client-java 实战案例 参考 本文首发于智客工坊-<socket.io客户端向webserve ...
- glassfish 一个bug重现
原文链接:https://java.net/jira/browse/GLASSFISH-21293?jql=project%20%3D%20GLASSFISH%20AND%20resoluti ...
- 抓到 Netty 一个 Bug,顺带来透彻地聊一下 Netty 是如何高效接收网络连接的
本系列Netty源码解析文章基于 4.1.56.Final版本 对于一个高性能网络通讯框架来说,最最重要也是最核心的工作就是如何高效的接收客户端连接,这就好比我们开了一个饭店,那么迎接客人就是饭店最重 ...
- Tomcat一个BUG造成CLOSE_WAIT
之前应该提过,我们线上架构整体重新架设了,应用层面使用的是Spring Boot,前段日子因为一些第三方的原因,略有些匆忙的提前开始线上的内测了.然后运维发现了个问题,服务器的HTTPS端口有大量的C ...
- MySQL关于exists的一个bug
今天碰到一个很奇怪的问题,关于exists的, 第一个语句如下: SELECT ) FROM APPLY t WHERE EXISTS ( SELECT r.APPLY_ID FROM RECORD ...
- 微软BI 之SSIS 系列 - MVP 们也不解的 Scrip Task 脚本任务中的一个 Bug
开篇介绍 前些天自己在整理 SSIS 2012 资料的时候发现了一个功能设计上的疑似Bug,在 Script Task 中是可以给只读列表中的变量赋值.我记得以前在 2008 的版本中为了弄明白这个配 ...
- [android开发IDE]adt-bundle-windows-x86的一个bug:无法解析.rs文件--------rs_core.rsh file not found
google的android自带的apps写的是相当牛逼的,将其导入到eclipse中方便我们学习扩展.可惜关于导入的资料太少了,尤其是4.1之后的gallery和camera合二为一了.之前导4.0 ...
- 记录一个使用HttpClient过程中的一个bug
最近用HttpClient进行链接请求,开了多线程之后发现经常有线程hang住,查看线程dump java.lang.Thread.State: RUNNABLE at java.net.Socket ...
- 应用服务器中对JDK的epoll空转bug的处理
原文链接:应用服务器中对JDK的epoll空转bug的处理 前面讲到了epoll的一些机制,与select和poll等传统古老的IO多路复用机制的一些区别,这些区别实质可以总结为一句话, 就是epol ...
随机推荐
- php获取当前是星期几
<?php $weekarray=array("日","一","二","三","四",&quo ...
- vue-cli 跳转方式
一:router-link标签跳转 <router-link to='/citylist'><div class="header-right">城市< ...
- Java 在 CMD 环境下编译
1. 未引用第三方 Jar 包类(该 Java 类默认无包名) # 定位到类存放地址 cd E:\tsgg # 编译命令 javac Test.java # 执行命令 java Test 2. 引用第 ...
- 初识SqlLite ---.net连接数据库
Sqlite 是一款轻量级的关系型数据库,以小巧和嵌入式闻名.以前只是听说,现在终于忍不住要尝试下.本文的初衷是为.net平台的使用者提供帮助. Sqlite有专门为VS2010开发的程序包,大家可以 ...
- 【手记】VSTO部署中的坑
局域网部署,客户机安装时报[部署清单签名的证书或其位置不受信任]: 在[Internet 属性]里(可运行inetcpl.cpl打开),把服务器内网IP加进[受信任站点]就好,不用管excel信任中心 ...
- jQuery点击页面其他部分隐藏下拉菜单
一.开发小要点 web页面中,我们一般不用select.option来实现下拉菜单效果,因为下拉框的样式丑且难以美化,所以我们选择控制ul显示隐藏来实现同样且高大上的效果,但是不能像下拉框那样点击页面 ...
- 【读书笔记】iOS-配件
如果你想用External Accessory框架开发第三方硬件设备,你需要考虑成为Made for iPhone(MFI)授权项目的成员. 得到授权的开发者可以获取技术资料,硬件设备以及技术支持,以 ...
- 【读书笔记】iOS-单元测试工具
一,单元测试工具. 1,OCUnit 苹果自带的. 2,GHUnit GHUnit是Objective-C语言里的另外一种单元测试工具. GHUnit- https://github.com/gh-u ...
- python之成员(面向对象)
1. 成员 在类中你能写的所有内容都是类的成员 class Person: def __init__(self, name, gender): self.name = name # 成员 self.g ...
- 手把手教你撸一个简易的 webpack
背景 随着前端复杂度的不断提升,诞生出很多打包工具,比如最先的grunt,gulp.到后来的webpack和Parcel.但是目前很多脚手架工具,比如vue-cli已经帮我们集成了一些构建工具的使用. ...