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

让我们来看看这段代码:
 

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);
}

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索java
并发
java 并发模型、java并发编程模型、java并发测试代码、java多线程并发测试、java 并发测试,以便于您获取更多的相关知识。

时间: 2024-09-04 17:32:31

在Java内存模型中测试并发程序代码_java的相关文章

Java内存模型FAQ(九)在新的Java内存模型中,final字段是如何工作的

原文:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html 第九章 译者:Alex 一个对象的final字段值是在它的构造方法里面设置的.假设对象被正确的构造了,一旦对象被构造,在构造方法里面设置给final字段的的值在没有同步的情况下对所有其他的线程都会可见.另外,引用这些final字段的对象或数组都将会看到final字段的最新值. 对一个对象来说,被正确的构造是什么意思呢?简单来说,它意味着这个正在构造的对象的引用在构造期

java内存模型中的happens-before的疑惑……

问题描述 我的问题有人提过,但是侧重点有不同,而且没人回答过:http://www.iteye.com/problems/54095jsr133里面定义到happens-before规则之一:"Each action in a thread happens-before every subsequent action in that thread.",翻译过来就是:"线程中的每个操作都happens-before这个线程中后续的所有操作."我们都知道为了效率问题,在

java 取出文本文件中空行的实例代码_java

复制代码 代码如下: import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.FileWriter;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader; public cl

《Java并发编程的艺术》一一第3章Java内存模型

第3章Java内存模型 Java线程之间的通信对程序员完全透明,内存可见性问题很容易困扰Java程序员,本章将揭开Java内存模型神秘的面纱.本章大致分4部分:Java内存模型的基础,主要介绍内存模型相关的基本概念:Java内存模型中的顺序一致性,主要介绍重排序与顺序一致性内存模型:同步原语,主要介绍3个同步原语(synchronized.volatile和f?inal)的内存语义及重排序规则在处理器中的实现:Java内存模型的设计,主要介绍Java内存模型的设计原理,及其与处理器内存模型和顺序

同步与Java内存模型(一)序言

原文:http://gee.cs.oswego.edu/dl/cpj/jmm.html 作者:Doug Lea 译者:萧欢  校对:丁一,方腾飞 先来看如下这个简单的Java类,该类中并没有使用任何的同步. 查看源代码 打印帮助 01 final class SetCheck { 02 private int  a = 0; 03 private long b = 0; 04   05 void set() { 06 a =  1; 07 b = -1; 08 } 09   10 boolean

全面理解Java内存模型

Java内存模型即Java Memory Model,简称JMM.JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式.JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的. 如果我们要想深入了解Java并发编程,就要先理解好Java内存模型.Java内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步.原始的Java内存模型效率并不是很理想,因此Java1.5版本对其进行了重构,现在的Java8仍沿用了Java1.5的版本. 关于并发编程 在并发

Java内存模型FAQ(三)JSR133是什么?

原文:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html 第三章 译者:Alex 从1997年以来,人们不断发现Java语言规范的17章定义的Java内存模型中的一些严重的缺陷.这些缺陷会导致一些使人迷惑的行为(例如final字段会被观察到值的改变)和破坏编译器常见的优化能力. Java内存模型是一个雄心勃勃的计划,它是编程语言规范第一次尝试合并一个能够在各种处理器架构中为并发提供一致语义的内存模型.不过,定义一个既一致又直

深入理解Java内存模型系列篇

[本文转载于深入理解Java内存模型,可点击每个章节标题查看原文] 深入理解Java内存模型(一)--基础 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线程之间必

深入理解Java内存模型(六)——final

与前面介绍的锁和volatile相比较,对final域的读和写更像是普通的变量访问.对于final域,编译器和处理器要遵守两个重排序规则: 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序. 下面,我们通过一些示例性的代码来分别说明这两个规则: public class FinalExample { int i; //普通变量 final