日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

为什么volatile不能保证原子性而Atomic可以?

發布時間:2024/1/17 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 为什么volatile不能保证原子性而Atomic可以? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在上篇《非阻塞同步算法與CAS(Compare and Swap)無鎖算法》中講到在Java中long賦值不是原子操作,因為先寫32位,再寫后32位,分兩步操作,而AtomicLong賦值是原子操作,為什么?為什么volatile能替代簡單的鎖,卻不能保證原子性?這里面涉及volatile,是java中的一個我覺得這個詞在Java規范中從未被解釋清楚的神奇關鍵詞,在Sun的JDK官方文檔是這樣形容volatile的:

The Java programming language provides a second mechanism, volatile fields, that is more convenient than locking for some purposes. A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable.

意思就是說,如果一個變量加了volatile關鍵字,就會告訴編譯器和JVM的內存模型:這個變量是對所有線程共享的、可見的,每次jvm都會讀取最新寫入的值并使其最新值在所有CPU可見。volatile似乎是有時候可以代替簡單的鎖,似乎加了volatile關鍵字就省掉了鎖。但又說volatile不能保證原子性(java程序員很熟悉這句話:volatile僅僅用來保證該變量對所有線程的可見性,但不保證原子性)。這不是互相矛盾嗎?

不要將volatile用在getAndOperate場合,僅僅set或者get的場景是適合volatile的

不要將volatile用在getAndOperate場合(這種場合不原子,需要再加鎖),僅僅set或者get的場景是適合volatile的

volatile沒有原子性舉例:AtomicInteger自增

例如你讓一個volatile的integer自增(i++),其實要分成3步:1)讀取volatile變量值到local; 2)增加變量的值;3)把local的值寫回,讓其它的線程可見。這3步的jvm指令為:

1234mov???0xc(%r10),%r8d ; Loadinc??? %r8d?????????? ; Incrementmov??? %r8d,0xc(%r10) ; Storelock addl $0x0,(%rsp) ; StoreLoad Barrier

注意最后一步是內存屏障。

什么是內存屏障(Memory Barrier)?

內存屏障(memory barrier)是一個CPU指令。基本上,它是這樣一條指令: a) 確保一些特定操作執行的順序; b) 影響一些數據的可見性(可能是某些指令執行后的結果)。編譯器和CPU可以在保證輸出結果一樣的情況下對指令重排序,使性能得到優化。插入一個內存屏障,相當于告訴CPU和編譯器先于這個命令的必須先執行,后于這個命令的必須后執行。內存屏障另一個作用是強制更新一次不同CPU的緩存。例如,一個寫屏障會把這個屏障前寫入的數據刷新到緩存,這樣任何試圖讀取該數據的線程將得到最新值,而不用考慮到底是被哪個cpu核心或者哪顆CPU執行的。

內存屏障(memory barrier)和volatile什么關系?上面的虛擬機指令里面有提到,如果你的字段是volatile,Java內存模型將在寫操作后插入一個寫屏障指令,在讀操作前插入一個讀屏障指令。這意味著如果你對一個volatile字段進行寫操作,你必須知道:1、一旦你完成寫入,任何訪問這個字段的線程將會得到最新的值。2、在你寫入前,會保證所有之前發生的事已經發生,并且任何更新過的數據值也是可見的,因為內存屏障會把之前的寫入值都刷新到緩存。

volatile為什么沒有原子性?

明白了內存屏障(memory barrier)這個CPU指令,回到前面的JVM指令:從Load到store到內存屏障,一共4步,其中最后一步jvm讓這個最新的變量的值在所有線程可見,也就是最后一步讓所有的CPU內核都獲得了最新的值,但中間的幾步(從Load到Store)是不安全的,中間如果其他的CPU修改了值將會丟失。下面的測試代碼可以實際測試voaltile的自增沒有原子性:

12345678910111213141516171819202122232425262728293031323334353637383940????private?static?volatile?long?_longVal = 0;?????????private?static?class?LoopVolatileimplements?Runnable {????????public?void?run() {????????????long?val = 0;????????????while?(val < 10000000L) {????????????????_longVal++;????????????????val++;????????????}????????}????}?????????private?static?class?LoopVolatile2implements?Runnable {????????public?void?run() {????????????long?val = 0;????????????while?(val < 10000000L) {????????????????_longVal++;????????????????val++;????????????}????????}????}?????????private??void?testVolatile(){????????Thread t1 = new?Thread(new?LoopVolatile());????????t1.start();?????????????????Thread t2 = new?Thread(new?LoopVolatile2());????????t2.start();?????????????????while?(t1.isAlive() || t2.isAlive()) {????????}????????System.out.println("final val is: "?+ _longVal);????}Output:-------------?????final?val is: 11223828final?val is: 17567127final?val is: 12912109

volatile沒有原子性舉例:singleton單例模式實現

這是一段線程不安全的singleton(單例模式)實現,盡管使用了volatile:

1234567891011121314public?class?wrongsingleton {????private?static?volatile?wrongsingleton _instance = null;????private?wrongsingleton() {}????public?static?wrongsingleton getInstance() {????????if?(_instance == null) {????????????_instance = new?wrongsingleton();????????}????????return?_instance;????}}

下面的測試代碼可以測試出是線程不安全的:

123456789101112131415161718192021222324252627282930313233public?class?wrongsingleton {????private?static?volatile?wrongsingleton _instance = null;????private?wrongsingleton() {}????public?static?wrongsingleton getInstance() {????????if?(_instance == null) {????????????_instance = new?wrongsingleton();????????????System.out.println("--initialized once.");????????}????????return?_instance;????}}private?static?void?testInit(){?????????????????Thread t1 = new?Thread(new?LoopInit());????????Thread t2 = new?Thread(new?LoopInit2());????????Thread t3 = new?Thread(new?LoopInit());????????Thread t4 = new?Thread(new?LoopInit2());????????t1.start();????????t2.start();????????t3.start();????????t4.start();?????????????????while?(t1.isAlive() || t2.isAlive() || t3.isAlive()|| t4.isAlive()) {?????????????????????}????}輸出:有時輸出"--initialized once."一次,有時輸出好幾次

原因自然和上面的例子是一樣的。因為volatile保證變量對線程的可見性,但不保證原子性

附:正確線程安全的單例模式寫法:

123456789@ThreadSafepublic?class?SafeLazyInitialization { ???private?static?Resource resource; ???public?synchronized?static?Resource getInstance() { ??????if?(resource == null)??????????resource = new?Resource();??????return?resource;????}}

另外一種寫法:

12345@ThreadSafepublic?class?EagerInitialization { ??private?static?Resource resource = new?Resource();??public?static?Resource getResource() { return?resource; } }

延遲初始化的寫法:

123456789@ThreadSafepublic?class?ResourceFactory { ????private?static?class?ResourceHolder { ????????public?static?Resource resource = new?Resource();????}????public?static?Resource getResource() { ????????return?ResourceHolder.resource ; ????}}

二次檢查鎖定/Double Checked Locking的寫法(反模式)

12345678910111213141516public?class?SingletonDemo {????private?static?volatile?SingletonDemo instance = null;//注意需要volatile??????private?SingletonDemo() {?? }??????public?static?SingletonDemo getInstance() {????????if?(instance == null) { //二次檢查,比直接用獨占鎖效率高???????????????synchronized?(SingletonDemo .class){????????????????????if?(instance == null) {???????????????????????????????instance = new?SingletonDemo (); ????????????????????}?????????????}????????}????????return?instance;????}}

為什么AtomicXXX具有原子性和可見性?

就拿AtomicLong來說,它既解決了上述的volatile的原子性沒有保證的問題,又具有可見性。它是如何做到的?當然就是上文《非阻塞同步算法與CAS(Compare and Swap)無鎖算法》提到的CAS(比較并交換)指令。 其實AtomicLong的源碼里也用到了volatile,但只是用來讀取或寫入,見源碼:

1234567891011121314151617public?class?AtomicLongextends?Numberimplements?java.io.Serializable {????private?volatile?long?value;????/**?????* Creates a new AtomicLong with the given initial value.?????*?????* @param initialValue the initial value?????*/????public?AtomicLong(long?initialValue) {????????value = initialValue;????}????/**?????* Creates a new AtomicLong with initial value {@code 0}.?????*/????public?AtomicLong() {????}

其CAS源碼核心代碼為:

123456789int?compare_and_swap (int* reg, int?oldval,int?newval){??ATOMIC();??int?old_reg_val = *reg;??if?(old_reg_val == oldval) ?????*reg = newval;??END_ATOMIC();??return?old_reg_val;}

虛擬機指令為:

1234mov???0xc(%r11),%eax?????? ; Loadmov??? %eax,%r8d??????????? inc??? %r8d???????????????? ; Incrementlock cmpxchg %r8d,0xc(%r11) ; Compare and exchange

因為CAS是基于樂觀鎖的,也就是說當寫入的時候,如果寄存器舊值已經不等于現值,說明有其他CPU在修改,那就繼續嘗試。所以這就保證了操作的原子性。

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的为什么volatile不能保证原子性而Atomic可以?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。