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. linux nohup

    nohup RAILS_ENV=production bundle exec XXXX & nohup RAILS_ENV=production bundle exec XXXX >/d ...

  2. openssl passwd

    openssl系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html 该伪命令用于生成加密的密码. [root@xuexi tmp]# whati ...

  3. Bootstrap-table 使用总结

    一.什么是Bootstrap-table? 在业务系统开发中,对表格记录的查询.分页.排序等处理是非常常见的,在Web开发中,可以采用很多功能强大的插件来满足要求,且能极大的提高开发效率,本随笔介绍这 ...

  4. .net Framework 源代码 · ScrollViewer

    本文是分析 .net Framework 源代码的系列,主要告诉大家微软做 ScrollViewer 的思路,分析很简单 看完本文,可以学会如何写一个 ScrollViewer ,如何定义一个 ISc ...

  5. CentOS配置VSFTP服务

    1.安装vsftpd a.查看是否安装vsftp [root@wsyjlly ~]# rpm -q vsftpd package vsftpd is not installed b.如果没有则安装vs ...

  6. Jedis工具类代码

    安装Redis可以参考 https://www.cnblogs.com/dddyyy/p/9763098.html Redis的学习可以参考https://www.cnblogs.com/dddyyy ...

  7. canvas-5Bezier-QuadraticCurveTo.html

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. Python 模块管理

    导入新的模块 创建一个 calculate.py 文件 print('ok') def add(x,y): return x + y def sub(x,y): return x - y 再创建一个 ...

  9. mysql之行(记录)的详细操作

    在Mysql管理软件中, 可以通过sql语句中的dml语言来实现数据的操作, 包括 使用INSERT实现数据的插入 UPDATE实现数据的更新 使用DELETE实现数据的删除 使用SELECT查询数据 ...

  10. python之线程相关操作

    1.线程: 一个进程可以有多个线程,共享一个进程的资源: 2.进程线程的区别:  进程是资源分配的最小单位,线程是程序执行的最小单位 3.python中线程模块threading, 提供的类: Thr ...