在Java內(nèi)存模型中測(cè)試并發(fā)程序代碼
讓我們來(lái)看看這段代碼:
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)); } }
問(wèn)題來(lái)了,這段代碼輸出的結(jié)果是什么呢?它究竟能輸出什么結(jié)果,上面的程序即使在崩潰的JVM上,仍然允許打印輸出什么結(jié)果呢?
讓我們來(lái)看看這個(gè)程序做了什么:
- 初始化了一個(gè)BitSet對(duì)象
- 兩個(gè)線程并行運(yùn)行,分別對(duì)第一和第二位的字段值設(shè)置為true
- 我們嘗試讓這兩個(gè)線程同時(shí)運(yùn)行。
- 讀取BitSet對(duì)象的值,然后輸出結(jié)果。
接下來(lái),我們需要構(gòu)造一些測(cè)試用例來(lái)檢查這些行為。顯然,其中一個(gè)只能運(yùn)行該例子,然后觀察結(jié)果,回答上面的問(wèn)題,可是,回答第二個(gè)關(guān)于允許輸出的結(jié)果,需要些技巧。
熟能生巧
幸運(yùn)的是,我們可以使用工具。 JCStress 就是一個(gè)為了解決這類問(wèn)題而產(chǎn)生的測(cè)試工具。
我們可以很容易地將我們的test case寫成JCStress可以識(shí)別的形式。事實(shí)上, 它已經(jīng)為我們準(zhǔn)備好了多種可能情況下的接口。我們需要一個(gè)例子,在這個(gè)例子中,2個(gè)線程并發(fā)地執(zhí)行,執(zhí)行的結(jié)果表示為2個(gè)布爾值。
我們使用一個(gè)Actor2_Arbiter1_Test<BitSet, BooleanResult2>接口, 它將為我們的2個(gè)線程提供一些方法塊和一個(gè)轉(zhuǎn)換方法,這個(gè)轉(zhuǎn)換方法將表示BitSet狀態(tài)的結(jié)果轉(zhuǎn)換成一對(duì)布爾值。我們需要找個(gè) Java 8 JVM 來(lái)運(yùn)行它, 但是現(xiàn)在這已經(jīng)不是什么問(wèn)題了.
看下面的實(shí)現(xiàn). 是不是特別簡(jiǎn)潔?
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(); } }
現(xiàn)在在運(yùn)行這個(gè)測(cè)試的時(shí)候,控制會(huì)去嘗試各種花樣以求獲取驅(qū)動(dòng)這些動(dòng)作的因素的所有可能組合: 并行的或者非并行的, 有和無(wú)負(fù)載檢測(cè)的, 還有一行中進(jìn)行許多許多次, 因此所有可能的結(jié)果都會(huì)被記錄到.
當(dāng)你想知道你的并行代碼是如何運(yùn)作的時(shí)候,這是比靠你自己去挖空心思想出所有細(xì)節(jié)更勝一籌的辦法.
此外,為了能利用到JCStress 約束帶來(lái)的全面性的便利,我們需要給它提供一個(gè)對(duì)可能結(jié)果的解釋. 要那樣做的話我們就需要使用如下所示的一個(gè)簡(jiǎn)單的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>
現(xiàn)在,我們已經(jīng)準(zhǔn)備好讓這頭野獸開始咆哮了. 通過(guò)使用下面的命令行運(yùn)行測(cè)試.
java -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:-RestrictContended -jar tests-custom/target/jcstress.jar -t=".*AnExampleTest"
而我們所得到的結(jié)果是一份優(yōu)雅的報(bào)告.
現(xiàn)在很清楚的是,我們不僅可以得到預(yù)期的結(jié)果,即兩個(gè)線程都已經(jīng)設(shè)置了它們的位,也遇到了一個(gè)競(jìng)爭(zhēng)條件,一個(gè)線程將覆蓋另一個(gè)線程的結(jié)果。
即使你看到發(fā)生了這種事情,也一定要有“山人自有妙計(jì)”的淡定心態(tài),不是嗎?
順便說(shuō)一下,如果你在思考如何修改這個(gè)代碼,答案是仔細(xì)閱讀 Javadoc 中的 BitSet 類,并意識(shí)到那并非是線程安全的,需要外部同步。這可以很容易地通過(guò)增加同步塊相關(guān)設(shè)定值來(lái)實(shí)現(xiàn)。
synchronized (bs) { bs.set(1); }
- 淺析Java內(nèi)存模型與垃圾回收
- Java 高并發(fā)三:Java內(nèi)存模型和線程安全詳解
- Java8內(nèi)存模型PermGen Metaspace實(shí)例解析
- Java內(nèi)存模型JMM詳解
- Java內(nèi)存模型與JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)的區(qū)別詳解
- Java內(nèi)存區(qū)域和內(nèi)存模型講解
- Java內(nèi)存模型之happens-before概念詳解
- Java內(nèi)存模型(JMM)及happens-before原理
- 細(xì)談java同步之JMM(Java Memory Model)
- 學(xué)習(xí)Java內(nèi)存模型JMM心得
- JAVA內(nèi)存模型(JMM)詳解
相關(guān)文章
Hibernate對(duì)數(shù)據(jù)庫(kù)刪除、查找、更新操作實(shí)例代碼
本篇文章主要介紹了Hibernate對(duì)數(shù)據(jù)庫(kù)刪除、查找、更新操作實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05Java8函數(shù)式接口Predicate用法示例詳解
這篇文章主要為大家介紹了Java8函數(shù)式接口Predicate用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07java實(shí)現(xiàn)的2048游戲完整實(shí)例
這篇文章主要介紹了java實(shí)現(xiàn)的2048游戲,結(jié)合完整實(shí)例形式分析了java實(shí)現(xiàn)2048游戲功能的相關(guān)數(shù)值運(yùn)算、swing組件布局、事件響應(yīng)等相關(guān)操作技巧,需要的朋友可以參考下2018-01-01Intellij IDEA 關(guān)閉和開啟自動(dòng)更新的提示?
這篇文章主要介紹了Intellij IDEA 關(guān)閉和開啟自動(dòng)更新的提示操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04使用Java實(shí)現(xiàn)系統(tǒng)托盤功能的介紹(附源碼以及截圖)
本篇文章介紹了,在Java中實(shí)現(xiàn)系統(tǒng)托盤功能的詳解,文中附源碼以及截圖介紹。需要的朋友參考下2013-05-05教你使用Java獲取當(dāng)前時(shí)間戳的詳細(xì)代碼
這篇文章主要介紹了如何使用Java獲取當(dāng)前時(shí)間戳,通過(guò)兩個(gè)java示例,向大家展示如何獲取java中的當(dāng)前時(shí)間戳,文本通過(guò)示例代碼給大家展示了java獲取當(dāng)前時(shí)間戳的方法,需要的朋友可以參考下2022-01-01springboot中RabbitMQ死信隊(duì)列的實(shí)現(xiàn)示例
死信隊(duì)列是一種特殊的消息隊(duì)列,用來(lái)存儲(chǔ)無(wú)法被正常消費(fèi)的消息,常被用來(lái)實(shí)現(xiàn)延遲處理,異常消息處理等,本文主要介紹了springboot中RabbitMQ死信隊(duì)列的實(shí)現(xiàn)示例,感興趣的可以了解一下2024-01-01