1. 程式人生 > >Java併發程式設計之驗證volatile不能保證原子性

Java併發程式設計之驗證volatile不能保證原子性

Java併發程式設計之驗證volatile不能保證原子性

通過系列文章的學習,凱哥已經介紹了volatile的三大特性。1:保證可見性 2:不保證原子性 3:保證順序。那麼怎麼來驗證可見性呢?本文凱哥(凱哥Java:kaigejava)將通過程式碼演示來證明為什麼說volatile不能夠保證共享變數的原子性操作。

我們來舉個現實生活中的例子:

中午去食堂打飯,假設你非常非常的飢餓,需要一葷兩素再加一份米飯。如果食堂打飯的阿姨再給你打一個菜的時候,被其他人打斷了,給其他人打飯,然後再回過頭給你打飯。你選一葷兩素再加一份米飯打完的過程被打斷了四次耗時30分鐘。你想想你自己的感受。是不是要瘋了,要暴走了!其實,如果把從你點菜到阿姨給你打完飯這個過程,看著計算機的一個執行緒執行過程的話,那麼在你點菜到你拿到飯菜這個過程是一個完整的,不能被打斷的,這就是所謂的原子性。如果被多次打斷的話想想你的心理,就知道程式如果在執行過程被打斷後的結果了。

原子性操作的定義:

所謂的原子性操作就是執行緒對變數的操作一旦開始,就會一直執行直到結束。中介不會因為其他原因而切換到另一個執行緒。操作是不可分割的,在執行完畢之前是不會被其他任務或是事件中斷的。一個操作或者是多個操作要麼執行都成功要麼執行都失敗(可以結合資料庫的原子性理解)。

怎麼證明volatile修飾的共享變數就不能保證原子性呢?

模擬場景:

共享變數volatile int number=0;執行number++操作。使用多個執行緒多次呼叫。看看使用volatile修飾的number在執行結束後的結果是否是我們預期的結果。

我們分別用10個執行緒執行100次,50個執行緒執行1000次以及50個執行緒執行一百萬次來看看結果。

先來看看變數是用volatil修飾的

再來看看主執行緒裡面:

按照上面咱們規定的執行緒數量執行次數來看看咱們預期結果和實際執行結果:

我們分別用10個執行緒執行100次,50個執行緒執行1000次以及50個執行緒執行一百萬次來

 

執行緒數量

執行次數

number預期結果

實際執行結果

10

100

10*100=1000

1000

50

1000

五萬

49297

200

1000

二十萬

194181

50

1000000

5千萬

7246921

 

 

從上面表格中我們可以看到,即時共享變數用volatile修飾了。但是隨著執行緒數量或者執行次數的增加,實際執行結果與預期結果相差越來越大。如果預期結果和執行結果一致則說明保證了原子性,但是從結果來看不是這樣的。從而證明了volatile的第二個特性:不能保證原子性。

為什麼從i++的執行結果上就能看出不保證原子性呢?

我們來分析:

正常來說200個執行緒,每個執行緒執行了1000次。最後應該輸出的是:200*1000=20000.二十萬。但是實際結果卻不是二十萬次。那說明了什麼呢?請看下圖:

 

說明:

主記憶體中有共享變數number的值是0,現在有4個CPU帶著4個執行緒都從主記憶體中copy變數到自己的工作區。這個是CPU1先競爭到然後再執行緒1的工作區中執行了number++.執行後將number的值更新成了1,寫回到主記憶體中了。這個時候正要或者正在通知其他CPU主記憶體中的number值變化了。CPU2和CPU3都收到通知了,將自己工作區的變數置為無效,重新從主記憶體獲取到number=1的值。這個時候CPU4執行的也快,在還沒有收到CPU1的通知的時候,就將自己執行後的number++的值也寫回到了主記憶體中。其實這個時候,cpu1執行緒1的操作還在進行中,但是因為cpu4執行緒4的操作打斷了執行緒1的操作。第一輪執行結果應該是4,但是因為執行緒4把執行緒1執行打斷了,將執行緒1執行結果覆蓋了。所以實際執行後的效果有可能是3或者2但是不可能是4.

從上分析結果,我們更能理解到volatile修飾的共享變數不能保證原子性了。因為有可能被其他執行緒打斷執行。

怎麼解決原子性問題呢?可以使用juc包下的atomic包下的物件就可以了。

Volatile的有序性證明,歡迎學習下一篇:《Java併發程式設計之驗證volatile指令重排-理論篇》

歡迎關注凱哥公眾號:凱哥Java(kaigejava)

相關推薦

Java併發程式設計驗證volatile不能保證原子

Java併發程式設計之驗證volatile不能保證原子性 通過系列文章的學習,凱哥已經介紹了volatile的三大特性。1:保證可見性 2:不保證原子性 3:保證順序。那麼怎麼來驗證可見性呢?本文凱哥(凱哥Java:kaigejava)將通過程式碼演示來證明為什麼說volatile不能夠保證共享變數的原子性操

java併發程式設計利用CAS保證操作的原子

import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class Counter { private AtomicInteger at

Java 併發程式設計Volatile原理剖析及使用

Java 併發程式設計之Volatile原理剖析及使用 在開始介紹Volatile之前,回顧一下在併發中極其重要的三個概念:原子性,可見行和有序性 原子性: 是指一個操作不可以被中斷.比如賦值操作a=1和返回操作return a,這樣的操作在JVM中只需要一步就可以完成,因此

Java 併發程式設計 volatile 關鍵字

作用 保證不同執行緒對 volatile 修飾的變數進行操作時的可見性,即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的。 禁止進行指令重排序。 volatile 的可見性 public class Test_09 {

Java併發程式設計volatile關鍵字

volatile這個關鍵字可能很多朋友都聽說過,或許也都用過。在Java 5之前,它是一個備受爭議的關鍵字,因為在程式中使用它往往會導致出人意料的結果。在Java 5之後,volatile關鍵字才得以重獲生機。   volatile關鍵字雖然從字面上理解起來比較簡單,但

Java併發程式設計原子操作類

原子操作類簡介 當更新一個變數的時候,多出現資料爭用的時候可能出現所意想不到的情況。這時的一般策略是使用synchronized解決,因為synchronized能夠保證多個執行緒不會同時更新該變數。然而,從jdk 5之後,提供了粒度更細、量級更輕,並且在多核處理器具有高效

Java 併發程式設計 volatile(三)

都是快取惹的禍害 快取一致性協議: 當某個CPU核心寫資料時,如果發現這個變數為共享變數,即在其他CPU快取中也有副本,就會通知其他CPU將改變數的快取置為無效, 其他CPU需要從記憶體重新讀取。 MESI 的狀態 案例: 資料有

JAVA併發程式設計Volatile變數

volatile:不穩定的;爆炸性的;反覆無常的volatile變數是java提供的一種弱同步機制(我覺得只能確保讀取的同步),當把變數宣告為volatile型別後:            1.編譯器與執行時都會注意到這個變數是共享的,會變的,不會將該變數上的操作與其他記憶體

Java併發程式設計 volatile

國慶已經結束了,本該在國慶前就應該做好這篇筆記的,怎奈自己太懶,就在今天把前幾天的知識梳理一下。在前幾篇的部落格介紹了一點併發程式設計的相關知識,今天我對volatile的原理簡單的闡述一下。 要想理解volatile的原理,需要對JMM(Java記憶體模式)

Java 併發程式設計(二):如何保證共享變數的原子

執行緒安全性是我們在進行 Java 併發程式設計的時候必須要先考慮清楚的一個問題。這個類在單執行緒環境下是沒有問題的,那麼我們就能確保它在多執行緒併發的情況下表現出正確的行為嗎? 我這個人,在沒有副業之前,一心撲在工作上面,所以處理的蠻得心應手,心態也一直保持的不錯;但有了副業之後,心態就變得像坐過山車一樣

Java併發程式設計CyclicBarrier

CyclicBarrier可以控制這樣的場景: 對多個執行緒,他們執行自己程式碼(執行run方法)的時間不一樣; 比如有3個執行緒,其run方法執行時間分別為1s, 2s, 3s。如果我們想在三個執行緒都完成自己的任務時執行相應的操作,CyclicBarrier就派上用場了。 寫了一

Java併發程式設計鎖機制LockSupport工具

關於文章涉及到的jdk原始碼,這裡把最新的jdk原始碼分享給大家----->jdk原始碼 前言 在上篇文章《Java併發程式設計之鎖機制之AQS(AbstractQueuedSynchronizer)》中我們瞭解了整個AQS的內部結構,與其獨佔式與共享式獲取同步狀態的實現

Java併發程式設計執行緒生命週期、守護執行緒、優先順序和join、sleep、yield

Java併發程式設計中,其中一個難點是對執行緒生命週期的理解,和多種執行緒控制方法、執行緒溝通方法的靈活運用。這些方法和概念之間彼此聯絡緊密,共同構成了Java併發程式設計基石之一。 Java執行緒的生命週期 Java執行緒類定義了New、Runnable、Running Man、Blocked和Dead

Java併發程式設計執行緒安全、執行緒通訊

Java多執行緒開發中最重要的一點就是執行緒安全的實現了。所謂Java執行緒安全,可以簡單理解為當多個執行緒訪問同一個共享資源時產生的資料不一致問題。為此,Java提供了一系列方法來解決執行緒安全問題。 synchronized synchronized用於同步多執行緒對共享資源的訪問,在實現中分為同步程

Java併發程式設計ThreadGroup

ThreadGroup是Java提供的一種對執行緒進行分組管理的手段,可以對所有執行緒以組為單位進行操作,如設定優先順序、守護執行緒等。 執行緒組也有父子的概念,如下圖: 執行緒組的建立 1 public class ThreadGroupCreator { 2 3 publi

Java併發程式設計Exchanger

概述   用於執行緒間資料的交換。它提供一個同步點,在這個同步點,兩個執行緒可以交換彼此的資料。這兩個執行緒通過exchange方法交換資料,如果第一個執行緒先執行exchange()方法,它會一直等待第二個執行緒也執行exchange方法,當兩個執行緒都到達同步點時,這兩個執行緒就可以交換資料

java併發程式設計使用 CountDownLatch 控制多個執行緒執行順序

有時候會有這樣的需求,多個執行緒同時工作,然後其中幾個可以隨意併發執行,但有一個執行緒需要等其他執行緒工作結束後,才能開始。舉個例子,開啟多個執行緒分塊下載一個大檔案,每個執行緒只下載固定的一截,最後由另外一個執行緒來拼接所有的分段,那麼這時候我們可以考慮使用CountDownLatch來控制併發。

JAVA併發程式設計基本概念

1、鎖是對物件訪問的時候,通過對物件加鎖,防止並行訪問的控制手段;對物件加鎖成功,代表我持有這個物件的監視器,解鎖,代表釋放了這個物件的監視器。 拿到物件的監視器,肯定是對物件加鎖成功的;對物件加鎖成功 ,程式可以主動Watiing或者Time_waiting在物件監視器上。 2、鎖與監

java併發程式設計happens-before原則,先行發生原則

下文為自己學習筆記。 關鍵詞理解: JMM:java memory model java記憶體模型 int a=1;//A int b=3;//B int c=a*b;//C 在上邊這段程式碼中,有A\B\C個語句 C依賴於A、B兩個語句,所以Ahappens-before於C,

Java併發程式設計鎖機制Condition介面

前言 在前面的文章中,我曾提到過,整個Lock介面下實現的鎖機制中AQS(AbstractQueuedSynchronizer,下文都稱之為AQS)與Condition才是真正的實現者。也就說Condition在整個同步元件的基礎框架中也起著非常重要的作用,既然它如此重要與犀利,那麼現在我