不晓得你能否这么感到,而关于我来讲最懊恼的bug就是处置并提问题。除非你曾经把握了应用其他Tools摒弃你现有的Tools. 可是,这里另有别的一个例子,由于我们曾经在这里讲述了非常多关于Java的常识,覆灭Tools并不是那末容易。
凡是我们需求把握非常多技能才干很好的了解并提问题,同时,也需求一些矫捷调试的才能和修复这些bug的聪明。现实上,此中一个最好的修复这类型bug的办法,就是深化了解你的Java顺序外部究竟做了些甚么。在这篇文章傍边,我们应用一个容易的并发顺序例子,来证实即便在这么容易的例子傍边,顺序运转的后果依然是难于意料。幸亏,在JVM生态系统中,有一些并发测试框架,叫做JCStress,经过这个框架,我们可以很好的猜测Java顺序的后果,同时可以直观的看到,究竟顺序做了甚么工作。
比来,我对一篇描绘的Java内存模子,也就是JMM,是怎么任务的文章:extremely insightful article,而觉得困惑。关于门外汉士, JMM, 参考Java Language Specification第17.4章节,是关于正式的说明了JVM是怎么处置内存拜访,另有,它答复了一个紧要的问题:
在给定的工夫点上,顺序的一个读取指令会前往甚么后果?
假如你可以读懂俄文,我引荐一个篇由Aleksei Shipilev写的一篇文章,“Pragmatics of Java Memory Model”。这篇文章讲述,JMM是甚么工具,为何它那末主要,另有为何并发编程那末庞杂。别的,毫无疑问,你可以参考其他关于这个主题的材料:
可是,如今,我不想花工夫去深化JMM,而是想经过一个例子,引见它的特色和主要性。
后来,我从Anton Arhipov那边发明了这个例子,他是JRebel的产品司理,善于于处理编程言语中碰到的奇异问题,另有Java和JVM。
让我们来看看这段代码:
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工具的值,然后输出后果。
假如你能答复上面的两个问题,祝贺你,你真的好凶猛。跟我发推特@shelajev让我看看你的凶猛哦亲。
接下来,我们需求结构一些测试用例来反省这些行动。明显,此中一个只能运转该例子,然后察看后果,答复上面的问题,可是,答复第二个关于答应输出的后果,需求些技能。
侥幸的是,我们可使用Tools。 JCStress 就是一个为了处理这类问题而发生的测试Tools。
我们可以很轻易地将我们的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>
如今,我们曾经预备好让这头野兽Start怒吼了. 经过运用下面的号令交运行测试.
java -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:-RestrictContended -jar tests-custom/target/jcstress.jar -t=".*AnExampleTest"
而我们所失掉的后果是一份优雅的陈述.
如今很明白的是,我们不只可以失掉预期的后果,即两个线程都曾经设置了它们的位,也碰到了一个竞争前提,一个线程将掩盖另外一个线程的后果。
即便你看到发作了这类工作,也必定要有“隐士自有奇策”的淡放心态,不是吗?
特地说一下,假如你在考虑怎么修正这个代码,谜底是细心浏览 Javadoc 中的 BitSet 类,并意想到那并不是是线程平安的,需求内部同步。这可以很轻易地经过增加同步块相干设定值来完成。
synchronized (bs) { bs.set(1); }
并发长短常庞杂的话题而且对并发顺序的推理需求非常多经历和技能。侥幸的是,Java生态系统曾经十分弱小强健并且有非常多有效的并发Tools。
特地说一句,假如你有本人的方法来处理并提问题,请鄙人面的评论平分享下,或者在我的 Twitter上联络我,很甘愿答应听到你的看法。
在这篇文章中我们看了一个容易的关于异步拜访BitSet的谜题,可是上面的办法异样实用于一切的Java并发谜题。 你可能偶尔发明很多文档中描绘的极端案例,可是谁读过呢,对吧?
因而不寒而栗的浏览Javadocs! 或者,你真的很爱好冒险,看看 Serkan Özal’s 关于Java中工具结构,立体图和不平安的 文章 吧。
本文中的一切译文仅用于进修和交换目标,转载请务必注明文章译者、出处、和本文链接。 2KB翻译任务按照 CC 协议,假如我们的任务有进犯到您的权益,请实时联络我们。2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务