在Java内存模型中测试并发程序代码
作者:goldensun 发布时间:2023-11-24 20:37:55
让我们来看看这段代码:
import java.util.BitSet;
import java.util.concurrent.CountDownLatch;
public class AnExample {
public static void main(String[] args) throws Exception {
BitSet bs = new BitSet();
CountDownLatch latch = new CountDownLatch(1);
Thread t1 = new Thread(new Runnable() {
public void run() {
try {
latch.await();
Thread.sleep(1000);
} catch (Exception ex) {
}
bs.set(1);
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
try {
latch.await();
Thread.sleep(1000);
} catch (Exception e) {
}
bs.set(2);
}
});
t1.start();
t2.start();
latch.countDown();
t1.join();
t2.join();
// crucial part here:
System.out.println(bs.get(1));
System.out.println(bs.get(2));
}
}
问题来了,这段代码输出的结果是什么呢?它究竟能输出什么结果,上面的程序即使在崩溃的JVM上,仍然允许打印输出什么结果呢?
让我们来看看这个程序做了什么:
初始化了一个BitSet对象
两个线程并行运行,分别对第一和第二位的字段值设置为true
我们尝试让这两个线程同时运行。
读取BitSet对象的值,然后输出结果。
接下来,我们需要构造一些测试用例来检查这些行为。显然,其中一个只能运行该例子,然后观察结果,回答上面的问题,可是,回答第二个关于允许输出的结果,需要些技巧。
熟能生巧
幸运的是,我们可以使用工具。 JCStress 就是一个为了解决这类问题而产生的测试工具。
我们可以很容易地将我们的test case写成JCStress可以识别的形式。事实上, 它已经为我们准备好了多种可能情况下的接口。我们需要一个例子,在这个例子中,2个线程并发地执行,执行的结果表示为2个布尔值。
我们使用一个Actor2_Arbiter1_Test<BitSet, BooleanResult2>接口, 它将为我们的2个线程提供一些方法块和一个转换方法,这个转换方法将表示BitSet状态的结果转换成一对布尔值。我们需要找个 Java 8 JVM 来运行它, 但是现在这已经不是什么问题了.
看下面的实现. 是不是特别简洁?
public class AnExampleTest implements
Actor2_Arbiter1_Test<BitSet, BooleanResult2> {
@Override
public void actor1(BitSet s, BooleanResult2 r) {
s.set(1);
}
@Override
public void actor2(BitSet s, BooleanResult2 r) {
s.set(2);
}
@Override
public void arbiter1(BitSet s, BooleanResult2 r) {
r.r1 = s.get(1);
r.r2 = s.get(2);
}
@Override
public BitSet newState() {
return new BitSet();
}
@Override
public BooleanResult2 newResult() {
return new BooleanResult2();
}
}
现在在运行这个测试的时候,控制会去尝试各种花样以求获取驱动这些动作的因素的所有可能组合: 并行的或者非并行的, 有和无负载检测的, 还有一行中进行许多许多次, 因此所有可能的结果都会被记录到.
当你想知道你的并行代码是如何运作的时候,这是比靠你自己去挖空心思想出所有细节更胜一筹的办法.
此外,为了能利用到JCStress 约束带来的全面性的便利,我们需要给它提供一个对可能结果的解释. 要那样做的话我们就需要使用如下所示的一个简单的XML文件.
<test name="org.openjdk.jcstress.tests.custom.AnExampleTest">
<contributed-by>Oleg Shelajev</contributed-by>
<description>
Tests if BitSet works well without synchronization.
</description>
<case>
<match>[true, true]</match>
<expect>ACCEPTABLE</expect>
<description>
Seeing all updates intact.
</description>
</case>
<case>
<match>[true, false]</match>
<expect>ACCEPTABLE_INTERESTING</expect>
<description>
T2 overwrites T1 result.
</description>
</case>
<case>
<match>[false, true]</match>
<expect>ACCEPTABLE_INTERESTING</expect>
<description>
T1 overwrites T2 result.
</description>
</case>
<unmatched>
<expect>FORBIDDEN</expect>
<description>
All other cases are unexpected.
</description>
</unmatched>
</test>
现在,我们已经准备好让这头野兽开始咆哮了. 通过使用下面的命令行运行测试.
java -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:-RestrictContended -jar tests-custom/target/jcstress.jar -t=".*AnExampleTest"
而我们所得到的结果是一份优雅的报告.
现在很清楚的是,我们不仅可以得到预期的结果,即两个线程都已经设置了它们的位,也遇到了一个竞争条件,一个线程将覆盖另一个线程的结果。
即使你看到发生了这种事情,也一定要有“山人自有妙计”的淡定心态,不是吗?
顺便说一下,如果你在思考如何修改这个代码,答案是仔细阅读 Javadoc 中的 BitSet 类,并意识到那并非是线程安全的,需要外部同步。这可以很容易地通过增加同步块相关设定值来实现。
synchronized (bs) {
bs.set(1);
}


猜你喜欢
- C语言字符串大小比较#include <stdio.h>#include <string.h>int fun(cha
- using System.IO;using System.IO.Compression;using System.Web;using Sys
- 本文实例讲述了java采用中文方式显示时间的方法。分享给大家供大家参考。具体如下:其中t为秒,比如有时候需要计算两个任务相差多久,或者该任务
- 说明:当程序中出现频繁变化的数据时,如果采用认为的方式进行修改并且编译打包则会导致代码的耦合性较高,不便于维护!所以能否为属性动
- 最近在折腾一些控制相关的软件设计,想起来状态机这个东西,对解决一些控制系统状态切换还是挺有用的。状态机(有限状态自动机)网上有很多介绍。简单
- 最近一门课要求编写一个上位机串口通信工具,我基于Java编写了一个带有图形界面的简单串口通信工具,下面详述一下过程,供大家参考 ^_^一:首
- Android SimpleAdapter使用详解HolderAdapter背景Android的AdapterView用的比较多,ListV
- 本文实例为大家分享了Java实现登录和注册的具体代码,供大家参考,具体内容如下登录和注册案例的分析:我们在完成一个需求时,需要面向对象,我们
- 1.会话会话: 用户打开了一个浏览器,点击了很多超链接,访问多个web次元,关闭浏览器,这个过程可以称之为会话有状态会话: 带有访问记录的会
- 同类型对象的比较三个维度去比较同一性相等性相似性样例引入想象一下这样的一个场景:小王去图书馆借了一本java核心技术卷1,如图不幸的是小王把
- 一、什么是Websocket?1.WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)2.它实
- 一、饿汉式单例类public class Singleton { privat
- 状态活动存放在一个叫返回栈的一个集合,当重新打开一个Activity时,它就会出现在栈顶。当要销毁该活动时,调用finish()或back,
- 1.剖析异或运算(^) 二元 ^ 运算符是为整型和 bool 类型预定义的。对于整型,^ 将计算操作数的按位“异或”。对于 bool 操作数
- 实现常驻通知栏时遇到的问题:无论如何就是不显示通知,查看日志发现貌似报错了:2020-06-28 14:11:34.923 6387-638
- 该接口实现了序列化,声明为 public interface Key extends SerializableKey 是所有密钥的顶层接口。
- 本文实例讲述了C#使用foreach语句遍历二维数组的方法。分享给大家供大家参考。具体分析如下:如果通过for语句循环遍历二维数组需要两重循
- 最近一直在使用邮件发送功能,老是遇到问题,后面才找到,原来并不是程序问题引起的,我吧问题整出来, javax.mail.SendFailed
- 一般文本文件我们以日志文件.log文件为例:import java.io.BufferedReader; import java.io.Fi
- 本文实例讲述了Android编程之绘图canvas基本用法。分享给大家供大家参考,具体如下:MainActivity的代码如下:packag