实现TOLock过程中的一处多线程bug
背景
最近在啃《多处理器编程的艺术》,书中的7.6节介绍了时限锁——实现了tryLock方法的队列锁。
书中重点讲解了tryLock的实现,也就是如何实现在等待超时后退出队列,放弃锁请求,并且能让后继线程感知到。
在实现的过程中,我为TOLock补充了lock方法的实现。代码如下所示:
public class TOLock implements Lock {
private static final QNode AVAILABLE = new QNode();
private AtomicReference<QNode> tail;
private ThreadLocal<QNode> myNode;
public TOLock() {
this.tail = new AtomicReference<>(null);
this.myNode = new ThreadLocal<>();
}
@Override
public void lock() {
QNode qNode = new QNode();
qNode.pred = null;
myNode.set(qNode);
QNode myPred = tail.getAndSet(qNode);
if (myPred == null) {
return;
}
while (myPred.pred != AVAILABLE) {
if (myPred.pred != null) {
myPred = myPred.pred;
}
}
}
@Override
public void unlock() {
QNode qNode = myNode.get();
if (!tail.compareAndSet(qNode, null)) {
qNode.pred = AVAILABLE;
}
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
long startTime = System.currentTimeMillis();
long patience = TimeUnit.MILLISECONDS.convert(time, unit);
QNode qNode = new QNode();
myNode.set(qNode);
qNode.pred = null;
QNode myPred = tail.getAndSet(qNode);
if (myPred == null || myPred.pred == AVAILABLE) {
return true;
}
while (System.currentTimeMillis() - startTime < patience) {
QNode predPred = myPred.pred;
if (predPred == AVAILABLE) {
return true;
} else if (predPred != null) {
myPred = predPred;
}
}
if (!tail.compareAndSet(qNode, myPred)) {
qNode.pred = myPred;
}
return false;
}
private static class QNode {
volatile QNode pred;
}
}
问题
在编写lock方法的单元测试的时候发现,TOLock偶现卡死,当加大线程池中线程数量,几乎是稳定复现卡死。遂debug,发现lock方法卡死是因为
while (myPred.pred != AVAILABLE) {
if (myPred.pred != null) {
myPred = myPred.pred;
}
}
这段代码中的myPred.pred为null,所以就一直走不出去。myPred.pred为什么会是null,再定睛一看,myPred居然就是AVAILABLE。这怎么会呢?
myPred的取值路径只有两种,一种是从tail通过GAS操作拿到tail之前的值,另一种就是走循环拿到myPred.pred。
原子引用tail一开始为null,每次都是吸一个new出来的QNode进队列,绝对不可能会有AVAILABLE的情况。
排除了所有不可能的情况,剩下的即使在不可能,也都是真相了。
事实上就是如此,myPred变为AVAILABLE是通过循环的if分支拿到的。
if (myPred.pred != null) {
myPred = myPred.pred;
}
这段while循环在多线程的情况下是有bug的,在并发环境下,在后继争用线程读取while条件的时候,有可能前驱线程还没有释放锁,所以此时的myPred.pred应该为null,接下去前驱线程释放了锁,此时myPred.pred为AVAILABLE,这时当前线程理应具备进入临界区的条件,但是因为内存循环的判断导致myPred被赋值为AVAILABLE,此后myPred.pred永远为null,线程无限时原地旋转,后继线程也无法进入临界区。
我随后将此处代码改为如下:
QNode predPred;
while ((predPred = myPred.pred) != AVAILABLE) {
if (predPred != null) {
myPred = predPred;
}
}
其实就和tryLock中的写法是类似的,将myPred.pred赋值到局部变量,封闭在栈上即可。
后记
后来仔细想了想,其实类似的写法在我以前读源码的时候我有注意到过,例如BufferedInputStream里面也有很多类似的将变量封闭在栈上保证线程安全的做法。可以参考这篇帖子。
在FilterInputStream里面定义的in是
protected volatile InputStream in
可以被子类访问到,或者BufferedInputStream本身的byte[] buf也是protected volatile的并且还被volatile修饰,本身就是为多线程环境设计的,所以我们可以看到
private InputStream getInIfOpen() throws IOException {
InputStream input = in;
if (input == null)
throw new IOException("Stream closed");
return input;
}
JDK中getInIfOpen是这样写的,虽然看上去挺丑的,像是冗余了一个input变量,但还真就是改不得。
否则就会出现在读取input的时候还不是null,return出去就已经是null的情况了。
看过类似代码,积累过知识,自己真正操刀练习的时候还是犯错,只说明了一句话:纸上得来终觉浅,绝知此事要躬行。
完整源码实现
关于TOLock的完整代码实现,可以参考我的github上的实现。
实现TOLock过程中的一处多线程bug的更多相关文章
- Coding过程中遇到的一些bug
1. 在使用layoutSubviews方法调整自定义view内部的子控件坐标时,最好不要使用子控件的centerX,centerY属性,否则会出现奇怪的bug. 如果一定要用,务必仔细检查,该子控件 ...
- vue 使用过程中自己遇到的bug
需要安装npm git(windows系统需要安装) npm 是node的包管理工具 npm 国内的网站比较慢,推荐使用cnpm(淘宝的镜像) cnpm(npm) install 创建依赖-----因 ...
- 写js过程中遇到的一个bug
<div class="func_Div" id="xxcx"><span>信息查询</span> ...
- Bug,项目过程中的重要数据
作者|孙敏 为什么要做Bug分析? Bug是项目过程中的一个有价值的虫子,它不只是给开发的,而是开给整个项目组的. 通过Bug我们能获得什么? 积累测试方法,增强QA的测试能力,提升产品质量 发现项目 ...
- 【转】C 编译器优化过程中的 Bug
C 编译器优化过程中的 Bug 一个朋友向我指出一个最近他们发现的 GCC 编译器优化过程(加上 -O3 选项)里的 bug,导致他们的产品出现非常诡异的行为.这使我想起以前见过的一个 GCC bug ...
- Junit使用过程中需要注意的诡异bug以及处理办法
在开发过程中我们有时会遇到狠多的问题和bug,对于在编译和运行过程中出现的问题很好解决,因为可以在错误日志中得到一定的错误提示信息,从而可以找到一些对应的解决办法.但是有时也会遇到一些比较诡异的问题和 ...
- ssd运行过程中遇到的bug
1.出现以下错误: 没有添加环境变量: https://github.com/weiliu89/caffe/issues/4 可以看到当前PYTHONPATH不再ssd1里面,所以需要修改,修改之后就 ...
- ltib安装过程中遇到好多问题,从网上转来的好多份总结
最近调试MPC5125的板子,第一步LTIB都装不过去,挫败感十足. LTIB的安装镜像来自于freescale的ltib-mpc5121ads-200906,是用于Ubuntu 10版本之前的,现在 ...
- ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开
ASP.NET MVC Filters 4种默认过滤器的使用[附示例] 过滤器(Filters)的出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每个请求都会响 ...
随机推荐
- 1113: [Poi2008]海报PLA
1113: [Poi2008]海报PLA Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 765 Solved: 466[Submit][Status ...
- JS实现购物车特效
学习通过JavaScript实现类似于淘宝的购物车效果,包括商品的单选.全选.删除.修改数量.价格计算.数目计算.预览等功能. 1. 实现兼容低版本IE的getElementsByClassName( ...
- JavaWeb之Filter过滤器
原本计划这一篇来总结JSP,由于JSP的内容比较多,又想着晚上跑跑步减减肥,所以今天先介绍Filter以及它的使用举例,这样的话还有些时间可以锻炼锻炼.言归正传,过滤器从字面理解她的话有拦网.过滤的功 ...
- PHP语言开发微信公众平台(订阅号)之开启开发者模式
(1)打开上一篇我们从花生壳官网获得的外网网址就会看到localhost根目录下的文件(这里不再赘述php环境的搭建).注:因为外网网址在能联网时,访问外网网址的任何人都能看到根目录下的所有文件,不仅 ...
- Java:从面试题“i++和++i哪个效率高?"开始学习java字节码
今天看到一道面试题,i++和++i的效率谁高谁低. 面试题的答案是++i要高一点. 我在网上搜了一圈儿,发现很多回答也都是同一个结论. 如果早个几年,我也会认同这个看法,但现在我负责任的说,这个结论是 ...
- webstorm入手笔记
一.webstorm学习前小记 webstorm是一款现在前端用的比较多的IDE,其优势也比较多,这个大家在网上随便搜搜就可以找到了.但是本人大部分的时间都是使用sublime text工作,最近由于 ...
- LVS的原理介绍
DR模式 LVS 的VIP 和 realserver 必须在同一个网段,不然广播后所有的包都会丢掉: 提前确认LVS/硬件LB 是什么模式,是否需要在同一个网段 所有的realserver 都必须绑 ...
- NumPy入门及基础
1.1 NumPy 数组对象 NumPy中的ndarray是一个多维数组对象,该对象由两部分组成: 实际的数据; 描述这些数据的元数据. 大部分的数组操作仅仅修改元数据部分,而不改变底层的实际 ...
- 自定义template
今天写代码写的有点烦了,感觉天天写new String(); new HashMap<String,String>()等,感觉写烦了,有没有快速的方法了.就你输入syso然后按atl+/就 ...
- placeholder的字体样式改变,滚动条的颜色改变,ios日期兼容
placeholder:::-webkit-input-placeholder { color: rgba(153, 153, 153, 0.541);font-size:12px;}:-moz-pl ...