在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"

而我们所得到的结果是一份优雅的报告.

在Java内存模型中测试并发程序代码

现在很清楚的是,我们不仅可以得到预期的结果,即两个线程都已经设置了它们的位,也遇到了一个竞争条件,一个线程将覆盖另一个线程的结果。

即使你看到发生了这种事情,也一定要有“山人自有妙计”的淡定心态,不是吗?

顺便说一下,如果你在思考如何修改这个代码,答案是仔细阅读 Javadoc 中的 BitSet 类,并意识到那并非是线程安全的,需要外部同步。这可以很容易地通过增加同步块相关设定值来实现。
 


synchronized (bs) {
bs.set(1);
}


标签:Java,并发
0
投稿

猜你喜欢

  • Android SharedPreferences存取操作以及封装详解

    2022-11-02 07:10:19
  • 详解Spring cloud使用Ribbon进行Restful请求

    2021-07-09 11:05:28
  • Java中的clone方法详解_动力节点Java学院整理

    2023-07-31 15:37:33
  • Android Studio实现帧动画

    2023-11-19 01:28:13
  • unity 如何修改材质属性和更换shader

    2023-02-22 12:42:44
  • Springboot整合knife4j与shiro的操作

    2023-08-25 08:31:42
  • MyBatis查询时属性名和字段名不一致问题的解决方法

    2023-10-23 16:56:36
  • JPA save()方法将字段更新为null的解决方案

    2023-10-28 22:29:28
  • C# null 合并运算符??(双问号)使用示例

    2022-12-03 02:29:19
  • unity 实现摄像机绕某点旋转一周

    2021-06-11 16:48:57
  • WinForm相对路径的陷阱

    2022-01-20 10:25:18
  • C#中ToString数据类型格式大全(千分符)

    2023-10-03 05:08:48
  • java关于并发模型中的两种锁知识点详解

    2023-09-16 02:05:34
  • SpringCloud-Alibaba-Nacos启动失败解决方案

    2023-12-19 05:34:45
  • Java 添加、读取和删除 Excel 批注的操作代码

    2023-10-28 21:55:36
  • Spring集成Quartz的简单配置的方法

    2023-08-24 02:52:27
  • Spring事件Application Event原理详解

    2021-10-03 19:43:45
  • JavaWeb开发之JSTL标签库的使用、 自定义EL函数、自定义标签(带属性的、带标签体的)

    2021-08-22 10:25:00
  • mybatis使用Integer类型查询可能出现的问题

    2022-09-01 12:47:38
  • 导入项目出现Java多个工程相互引用异常A cycle was detected in the build path of project的解决办法

    2023-06-26 16:27:17
  • asp之家 软件编程 m.aspxhome.com