程序員:不能逃避的synchronize和volatile

本博客 貓叔的博客,轉載請申明出處java

閱讀本文約 「10分鐘」git

適讀人羣:Java 初級github

學習筆記,我也是呆呆作了很久,學了一下PS,而後繼續思考了一會,再開始寫出來的,但願能夠簡明易懂。緩存

原子性

首先是咱們彼此都要保持一致的觀點:原子(Atomic)操做指相應的操做是單一不可分割的操做多線程

emmmm,這裏很牽強的解釋下原子性,仍是不懂就搜搜其餘文章,最好看看一些具體的例子架構

首先是代碼例子學習

對int型變量conut執行counter++的操做不是原子操做優化

這能夠分爲3個操做spa

  • 一、讀取變量counter的當前值
  • 二、拿counter當前值和1作加法運算
  • 三、將counter的當前值增長1後賦值給counter變量

上面的步驟2,頗有可能在執行的時候就已經被其餘線程修改了,其所爲的「當前值」已是過時的線程

或者看看百度百科的例子

咱們以decl (遞減指令)爲例,這是一個典型的"讀-改-寫"過程,涉及兩次內存訪問。設想在不一樣CPU運行的兩個進程都在遞減某個計數值,可能發生的狀況是:

  • ⒈ CPU A(CPU A上所運行的進程,如下同)從內存單元把當前計數值⑵裝載進它的寄存器中;
  • ⒉ CPU B從內存單元把當前計數值⑵裝載進它的寄存器中。
  • ⒊ CPU A在它的寄存器中將計數值遞減爲1;
  • ⒋ CPU B在它的寄存器中將計數值遞減爲1;
  • ⒌ CPU A把修改後的計數值⑴寫回內存單元。
  • ⒍ CPU B把修改後的計數值⑴寫回內存單元。

內存裏的計數值應該是0,然而它倒是1。兩個進程都去掉了對該共享資源的引用,但沒有一個進程可以釋放它--兩個進程都推斷出:計數值是1,共享資源仍然在被使用

我再舉例我呆想到的例子,一個姐姐和一個妹妹一塊兒包餃子

image

畫的很通常,別看我這樣,我也是學過2小時速成素描的·····

假設咱們在一個黑盒環境下,就是兩姐妹都在各自小空間包餃子,而後她們把餃子經過各自的小洞口放入一個大盒子裏。她們並不知道對方(好比她們兩剛剛由於媽媽不給零花錢而生氣了)

這個時候她們各自同時邊賭氣邊包了一個餃子,同時放到盒子裏,媽媽跑過來問老大,盒子裏有多少個了?她只知道一個。再問問老二,她也是回答一個。這個生活例子可能提交特殊,不過偶爾生活中由於信息不對稱而致使的預知結果與實際有誤差也是常常發生的

因此他們腦海就是這個狀況。其實盒子裏已是2個餃子了

image

那麼其實這個場景也像是JVM

image

synchronize

synchronize關鍵字能夠實現操做的原子性,其本質是經過該關鍵字所包括的臨界區的排他性保證在任何一個時刻只有一個線程可以執行臨界區中的代碼

也就是說,如今媽媽說只有聽她的,兩姐妹纔能有零花錢,因此她叫兩個鬧脾氣的小鬼都到廚房,並拿出了大盒子,讓她們從新開始,不過要按照媽媽的要求來

image

媽媽先讓姐姐包了5個,由於兩姐妹都在廚房,不是各自在房間,因此此次妹妹都看在眼裏,接着媽媽讓妹妹包10個,妹妹顯然是有點不樂意了(憑什麼我姐才5個),不過她仍是老實作了,如今他們三人都知道盒子裏有15個

這裏就又牽出了synchronize的另外一個特色,保證內存的可見性

它保證了一個線程執行臨界區中的代碼時所修改的變量值對於稍有執行該臨界區中的代碼的線程來講是可見的,這對於保證多線程的代碼是很是重要的

官方的解釋下:CPU執行代碼,爲了減小變量訪問的消耗,會將值緩存到CPU緩存區,再次訪問的時候,就是從緩存區去讀取而不是主內存,這裏的緩存區有點相似姐姐腦海/妹妹腦海。並且代碼對緩存區的修改可能僅修改緩存區,沒有被寫回主內存。因爲CPU都有本身的存儲區,對於不一樣CPU的存儲區內容是不可見的。這也是所謂的內存可見性

volatile

一樣這個兄弟也能夠保證內存可見性

一個線程對於一個採用volatile修改的變量的值的更改對於其餘訪問該變量的值的線程老是可見的

若是說對比synchronize和volatile的內存鎖,而後說volatile是輕量級鎖,emmmm,很差不太恰當

volatile的內部鎖並不能保證操做的原子性。

他在內存可見性的核心機制是:修改的值會被寫入主內存,且其餘CPU緩存區的值會所以失效(而後再更新一個最新值),保證其餘線程訪問volatile修飾的變量老是最新值。

固然他也有一個核心做用:禁止指令重排序(Re-order)

大家通常怎麼寫5的?

image

假如以上是咱們的規定與但願

可能編譯器和CPU爲了提供指令的執行效率可能會進行指令重排序(優化)

image

若是你但願它是按照規定來的話就加上volatile,雖然可能會致使編譯器和CPU沒法對一些指令作可能的優化,假設上面那樣寫對於計算機來講算優化:)

用程序來寫一個例子:

private SomeOne object = new SomeOne();
複製代碼

你先想一下,你以爲的順序,好了,我說說計算機可能的順序

  • 一、分配一段用於存儲SomeOne的內存空間
  • 二、對該內存空間引用賦值給變量object
  • 三、建立類SomeOne

若是當其餘線程訪問二、object變量的時候,僅獲得一個指向存儲SomeOne存儲空間的引用,由於三、SomeOne還沒建立

結語

但願各位兄弟能看到一些新的風景,synchronize能夠保證操做原子性,且保證內存可見性;volatile僅能保證內存可見性。

synchronize會致使上下文切換,volatile不會哦。

關於上下文切換的,能夠去看公衆號的上一篇文章

我是MySelf,還在堅持學習技術與產品經理相關的知識,但願本文能給你帶來新的知識點。

公衆號:Java貓說

學習交流羣:728698035

現架構設計(碼農)兼創業技術顧問,不羈平庸,熱愛開源,雜談程序人生與不按期乾貨。

Image Text
相關文章
相關標籤/搜索