在Java內(nèi)存模型中測試并發(fā)程序代碼
讓我們來看看這段代碼:
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));
}
}
問題來了,這段代碼輸出的結(jié)果是什么呢?它究竟能輸出什么結(jié)果,上面的程序即使在崩潰的JVM上,仍然允許打印輸出什么結(jié)果呢?
讓我們來看看這個(gè)程序做了什么:
- 初始化了一個(gè)BitSet對象
- 兩個(gè)線程并行運(yùn)行,分別對第一和第二位的字段值設(shè)置為true
- 我們嘗試讓這兩個(gè)線程同時(shí)運(yùn)行。
- 讀取BitSet對象的值,然后輸出結(jié)果。
接下來,我們需要構(gòu)造一些測試用例來檢查這些行為。顯然,其中一個(gè)只能運(yùn)行該例子,然后觀察結(jié)果,回答上面的問題,可是,回答第二個(gè)關(guān)于允許輸出的結(jié)果,需要些技巧。
熟能生巧
幸運(yùn)的是,我們可以使用工具。 JCStress 就是一個(gè)為了解決這類問題而產(chǎn)生的測試工具。
我們可以很容易地將我們的test case寫成JCStress可以識別的形式。事實(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)換成一對布爾值。我們需要找個(gè) Java 8 JVM 來運(yùn)行它, 但是現(xiàn)在這已經(jīng)不是什么問題了.
看下面的實(shí)現(xià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è)測試的時(shí)候,控制會去嘗試各種花樣以求獲取驅(qū)動這些動作的因素的所有可能組合: 并行的或者非并行的, 有和無負(fù)載檢測的, 還有一行中進(jìn)行許多許多次, 因此所有可能的結(jié)果都會被記錄到.
當(dāng)你想知道你的并行代碼是如何運(yùn)作的時(shí)候,這是比靠你自己去挖空心思想出所有細(xì)節(jié)更勝一籌的辦法.
此外,為了能利用到JCStress 約束帶來的全面性的便利,我們需要給它提供一個(gè)對可能結(jié)果的解釋. 要那樣做的話我們就需要使用如下所示的一個(gè)簡單的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)備好讓這頭野獸開始咆哮了. 通過使用下面的命令行運(yùn)行測試.
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è)競爭條件,一個(gè)線程將覆蓋另一個(gè)線程的結(jié)果。
即使你看到發(fā)生了這種事情,也一定要有“山人自有妙計(jì)”的淡定心態(tài),不是嗎?
順便說一下,如果你在思考如何修改這個(gè)代碼,答案是仔細(xì)閱讀 Javadoc 中的 BitSet 類,并意識到那并非是線程安全的,需要外部同步。這可以很容易地通過增加同步塊相關(guān)設(shè)定值來實(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)文章
基于Java實(shí)現(xiàn)ssh命令登錄主機(jī)執(zhí)行shell命令過程解析
這篇文章主要介紹了基于Java實(shí)現(xiàn)ssh命令登錄主機(jī)執(zhí)行shell命令過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
spring kafka框架中@KafkaListener 注解解讀和使用案例
Kafka 目前主要作為一個(gè)分布式的發(fā)布訂閱式的消息系統(tǒng)使用,也是目前最流行的消息隊(duì)列系統(tǒng)之一,這篇文章主要介紹了kafka @KafkaListener 注解解讀,需要的朋友可以參考下2023-02-02
java如何根據(jù)提供word模板導(dǎo)出word文檔詳解
在日常的開發(fā)工作中,我們時(shí)常會遇到導(dǎo)出Word文檔報(bào)表的需求,比如公司的財(cái)務(wù)報(bào)表、醫(yī)院的患者統(tǒng)計(jì)報(bào)表、電商平臺的銷售報(bào)表等等,這篇文章主要給大家介紹了關(guān)于java如何根據(jù)提供word模板導(dǎo)出word文檔的相關(guān)資料,需要的朋友可以參考下2023-09-09
解析SpringSecurity自定義登錄驗(yàn)證成功與失敗的結(jié)果處理問題
這篇文章主要介紹了SpringSecurity系列之自定義登錄驗(yàn)證成功與失敗的結(jié)果處理問題,本文通過實(shí)例給大家講解的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11
學(xué)習(xí)Java的static與final關(guān)鍵字
本篇文章給大家詳細(xì)分析了Java的static與final關(guān)鍵字知識點(diǎn)以及相關(guān)代碼分享,有需要的讀者跟著學(xué)習(xí)下吧。2018-03-03
SpringBoot實(shí)現(xiàn)Server-Sent Events(SSE)的使用完整指南
使用SpringBoot實(shí)現(xiàn)Server-Sent Events(SSE)可以有效處理實(shí)時(shí)數(shù)據(jù)推送需求,具有單向通信、輕量級和高實(shí)時(shí)性等優(yōu)勢,本文詳細(xì)介紹了在SpringBoot中創(chuàng)建SSE端點(diǎn)的步驟,并通過代碼示例展示了客戶端如何接收數(shù)據(jù),適用于實(shí)時(shí)通知、數(shù)據(jù)展示和在線聊天等場景2024-09-09
SpringBoot整合Drools規(guī)則引擎動態(tài)生成業(yè)務(wù)規(guī)則的實(shí)現(xiàn)
本文主要介紹了SpringBoot整合Drools規(guī)則引擎動態(tài)生成業(yè)務(wù)規(guī)則的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12

