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. 一个bug肝一周...忍不住提了issue

    导航 Socket.IO是什么 Socket.IO的应用场景 为什么选socket.io-client-java 实战案例 参考 本文首发于智客工坊-<socket.io客户端向webserve ...

  2. glassfish 一个bug重现

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

  3. 抓到 Netty 一个 Bug,顺带来透彻地聊一下 Netty 是如何高效接收网络连接的

    本系列Netty源码解析文章基于 4.1.56.Final版本 对于一个高性能网络通讯框架来说,最最重要也是最核心的工作就是如何高效的接收客户端连接,这就好比我们开了一个饭店,那么迎接客人就是饭店最重 ...

  4. Tomcat一个BUG造成CLOSE_WAIT

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

  5. MySQL关于exists的一个bug

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

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

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

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

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

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

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

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

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

随机推荐

  1. 这次聊聊Promise对象

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由前端林子发表于云+社区专栏 Promise是CommonJS提出的一种规范,在ES6中已经原生支持Promise对象,非ES6环境可以 ...

  2. [Golang] GoConvey测试框架使用指南

    GoConvey 是一款针对Golang的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多 Web 界面特性. GoConvey 网站 : http://smartystreet ...

  3. PHP的UTF-8中文转拼音处理类

    <?php /** * PHP 汉字转拼音 * @author Jerryli(hzjerry@gmail.com) * @version V0.20140715 * @package SPFW ...

  4. jenkins+Android+gradle持续集成

    本文Android自动化打包采用jenkins+gradle+upload to pyger的方式来实现,job执行完后只需要打开链接扫描二维码即可下载apk. 一.环境准备 1.下载Android ...

  5. Spark新手入门——3.Spark集群(standalone模式)安装

    主要包括以下三部分,本文为第三部分: 一. Scala环境准备 查看二. Hadoop集群(伪分布模式)安装 查看三. Spark集群(standalone模式)安装 Spark集群(standalo ...

  6. [POI2006] PRO-Professor Szu

    Description \(n\) 个别墅以及一个主建筑楼,从每个别墅都有很多种不同方式走到主建筑楼,其中不同的定义是(每条边可以走多次,如果走边的顺序有一条不同即称两方式不同). 询问最多的不同方式 ...

  7. SSL连接并非完全问题解决

    教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步>>> (原文)SSL 连接并非完全安全问题解决. 更多讨论或者错误提交,也请移步. 最近拿到了 TrustAsia ...

  8. 浅析"WeixinJSBridge is not defined"

    Fundebug并没有使用微信 JS-SDK,然而却收到了WeixinJSBridge is not defined的报错: 我们的用户也收到了类似的错误报警,并且很多开发者都遇到类似的问题: 我的微 ...

  9. centos7学习笔记-安装后的一些配置

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 一.允许普通用户使用sudo root用户下运行 visudo 在root ALL=(ALL)ALL下面添加 username ...

  10. 【工具相关】Web-Sublime Text2的用法(一)

    一,打开Sublime Text2--->出现如下所示界面. 二,在编辑区域可以随便输入数字.如图所示. 三,File--->Save. 四,将名字加上后缀,使其成为我们希望编辑的文件类型 ...