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

 更新时间:2015年07月10日 11:19:48   投稿:goldensun  
这篇文章主要介绍了在Java内存模型中测试并发程序代码,辅以文中所提到的JavaScript库JCStress进行,需要的朋友可以参考下

让我们来看看这段代码:
 

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"

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

2015710111243724.png (955×280)

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

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

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

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

相关文章

  • springboot实现返回视图而不是string的方法

    springboot实现返回视图而不是string的方法

    这篇文章主要介绍了springboot实现返回视图而不是string的方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • springboot在服务器上的几种启动方式(小结)

    springboot在服务器上的几种启动方式(小结)

    这篇文章主要介绍了springboot在服务器上的几种启动方式(小结),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • 如何安装java的运行环境IDEA

    如何安装java的运行环境IDEA

    这篇文章主要介绍了如何安装java的运行环境IDEA,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • 基于opencv+java实现简单图形识别程序

    基于opencv+java实现简单图形识别程序

    这篇文章主要给大家介绍了如何基于opencv+java实现简单图形识别程序的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-01-01
  • maven-assembly-plugin报红无法加载报错:Plugin ‘maven-assembly-plugin:‘ not found

    maven-assembly-plugin报红无法加载报错:Plugin ‘maven-assembly-plugin

    maven-assembly-plugin是一个常用的打包插件,但是在使用过程中经常会遇到各种报错,本文就来介绍一下maven-assembly-plugin报红无法加载报错,具有一定的参考价值
    2023-08-08
  • Java中使用patchca生成超炫的验证码

    Java中使用patchca生成超炫的验证码

    这篇文章主要介绍了Java中使用patchca生成超炫的验证码 的相关资料,需要的朋友可以参考下
    2017-06-06
  • 一文带你学会Java网络编程

    一文带你学会Java网络编程

    网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。这篇文章将带大家深入了解一下Java的网络编程,需要的可以了解一下
    2022-08-08
  • mybatis-plus用insertBatchSomeColumn方法批量新增指定字段

    mybatis-plus用insertBatchSomeColumn方法批量新增指定字段

    mybatisPlus底层的新增方法是一条一条的新增的,下面这篇文章主要给大家介绍了关于mybatis-plus用insertBatchSomeColumn方法批量新增指定字段的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • java多线程中的生产者和消费者队列详解

    java多线程中的生产者和消费者队列详解

    这篇文章主要介绍了java多线程中的生产者和消费者队列详解,队列,是一种数据结构,除了优先级队列和LIFO队列外,队列都是以FIFO(先进先出)的方式对各个元素进行排序的,需要的朋友可以参考下
    2024-01-01
  • Java中如何使用正则表达式提取各种类型括号中的内容

    Java中如何使用正则表达式提取各种类型括号中的内容

    最近在工作中遇到一个问题,就是需要一个字符串中每一个中括号里的内容,下面这篇文章主要给大家介绍了关于Java中如何使用正则表达式提取各种类型括号中的内容,需要的朋友可以参考下
    2023-06-06

最新评论