日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

为什么我们需要volatile关键字?

發(fā)布時間:2025/3/21 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 为什么我们需要volatile关键字? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

volatile字段以確保多個線程始終看到最新值,即使緩存系統(tǒng)或編譯器優(yōu)化正在起作用。從volatile變量讀取始終返回此變量的最新寫入值。java.uti.concurrent包中的大多數(shù)類的方法也具有此屬性。通常在內(nèi)部使用volatile字段。

關(guān)于volatile關(guān)鍵字讓我著迷的是它是必須的,因為我的軟件仍然在硅芯片上運行。即使我的應(yīng)用程序在Java虛擬機中的虛擬機上運行在云中。但是,盡管所有這些軟件層都抽象掉底層硬件,但由于我的軟件運行的處理器緩存,仍然需要volatile關(guān)鍵字。

處理器會在每個內(nèi)核緩存中緩存主內(nèi)存值,這樣提高內(nèi)存訪問性能。雖然從CPU寄存器讀取大約300皮秒,但從主存儲器讀取需要50-100納秒。通過使用高速緩存,可以減少到大約1納秒。

現(xiàn)在問題是核心應(yīng)該何時檢查緩存的值是否在另一個核的緩存中被修改了,這是由volatile字段注釋完成的。通過將字段聲明為volatile,我們告訴JVM,當線程讀取volatile字段時,我們希望看到最新的寫入值。JVM使用特殊指令告訴CPU它應(yīng)該同步其緩存。對于x86處理器系列,這些指令稱為內(nèi)存屏障,如此處所述。

處理器不僅可以同步volatile字段的值,還可以同步整個緩存。因此,如果我們從volatile字段讀取,我們會看到其他內(nèi)核上的所有寫入此變量以及寫入volatile變量之前寫入這些內(nèi)核的值。

測試

現(xiàn)在讓我們看看它在實踐中是如何運作的。讓我們看看當我們使用沒有volatile注釋的字段時我們是否讀取過時的值:

public?class?Termination?{private?int?v;public?void?runTest()?throws?InterruptedException?{Thread workerThread =?new?Thread( () -> {while(v ==?0) {// spin}});workerThread.start();v =?1;workerThread.join();?// test might hang up here}public?static?void?main(String[] args)??throws?InterruptedException?{for(int?i =?0?; i <?1000?; i++) {new?Termination().runTest();}} }

當在一個核中寫入線程實現(xiàn)更新字段v,同時讀取線程在另一個線程中讀取字段v時,測試時應(yīng)該掛起并永遠運行。但至少當我在我的機器上運行測試時,測試永遠不會掛起。原因是測試需要很少的CPU周期,兩個線程通常在同一個內(nèi)核上運行。當兩個線程在同一個內(nèi)核上運行時,它們會讀取并寫入同一個緩存。

幸運的是,OpenJDK提供了一個工具jcstress,它可以幫助進行這類測試。jcstress使用多個技巧,測試的線程在不同的核心上運行。這里上面的例子被重寫為jcstress測試:

@JCStressTest(Mode.Termination) @Outcome(id =?"TERMINATED", expect = Expect.ACCEPTABLE, desc =?"Gracefully finished.") @Outcome(id =?"STALE", expect = Expect.ACCEPTABLE_INTERESTING, desc =?"Test hung up.") @State public?class?APISample_03_Termination?{int?v;@Actorpublic?void?actor1()?{while?(v ==?0) {// spin}}@Signalpublic?void?signal()?{v =?1;} }

此測試來自jcstress示例。通過使用注釋@JCStressTest注釋類,我們告訴jcstress這個類是一個jcstress測試。jcstress在一個單獨的線程中運行用@Actor和@Signal注釋的方法。jcstress首先啟動actor線程,然后運行信號線程。如果測試在合理的時間內(nèi)退出,jcstress會記錄“TERMINATED”結(jié)果,否則結(jié)果為“STALE”。

我已經(jīng)在我的開發(fā)機器上運行了這個測試,一次使用普通測試,一次使用volatile字段v。對于volatile字段的測試看起來像這樣:

public?class?APISample_03_Termination?{volatile?int?v;// methods omitted }

jcstress使用不同的JVM參數(shù)多次運行測試用例。

測試結(jié)果表明:使用沒有volatile注釋的字段確實會掛起線程。掛起線程的百分比取決于JVM標志和環(huán)境,JDK版本等。

何時使用volatile字段

volatile字段的另一種用法是使用volatile字段進行讀取和鎖定以進行寫入。或者您可以將它們與JDK 9 VarHandle一起使用以實現(xiàn)原子操作。這里描述了如何實現(xiàn)這些技術(shù)。

與happens-before相關(guān)

一般情況下人們不直接使用volatile字段。我寧愿使用java.util.concurrent包中的數(shù)據(jù)結(jié)構(gòu)進行并發(fā)編程。其中內(nèi)部使用volatile字段。

在這些類的文檔中,我們經(jīng)常閱讀關(guān)于內(nèi)存一致性效果的事情,與happens-before有關(guān):

內(nèi)存一致性效果:happen-before異步計算所采取的操作發(fā)生在另一個線程中相應(yīng)的Future.get()之后。

現(xiàn)在,憑借我們對volatile字段的了解,我們可以解碼此文檔。如果我們從volatile字段讀取,我們會看到其他內(nèi)核上的所有寫入此變量。用java.util.concurrent文檔的話來說,我們會說對volatile變量的讀取會創(chuàng)建happen-before關(guān)系到此變量的寫入。

所以上面的語句意味著調(diào)用Future.get()的線程總是會讀取在另外一個線程調(diào)用Future接口方法的寫入的最新寫入值。

我們使用FutureTask類在兩個線程之間傳輸數(shù)據(jù)作為示例。FutureTask實現(xiàn)接口Future,因此調(diào)用方法FutureTask.get()總是能看到通過另一個方法(例如FutureTask.set())寫入的最新值。

用于檢測缺少的volatile注釋的工具

如果您忘記將字段聲明為volatile,則線程可能會讀取過時的值。但是在測試期間看到這個的機會相當?shù)汀S捎谧x取和寫入必須幾乎在同一時間并且在不同的核心上發(fā)生以讀取過時值,因此這僅在重負載和長時間運行之后發(fā)生,例如在生產(chǎn)中。

因此,在測試運行中存在檢測此類問題的工具并不奇怪:

ThreadSanitizer可以檢測C ++程序中缺少的volatile注釋。有一個Java增強提議草案,JEP草案:Java Thread Sanitizer將ThreadSanitizer包含在OpenJDK JVM中。這將允許我們在JVM中以及在JVM執(zhí)行的Java應(yīng)用程序中找到缺少的volatile注釋。

vmlens是我編寫的用于測試并發(fā)java的工具,它可以檢測Java測試運行中缺少的volatile注釋。

總結(jié)

以上是生活随笔為你收集整理的为什么我们需要volatile关键字?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。