volatile和synchronized的區別

談這兩個的區別,首先須要理解線程安全的兩個方面:執行控制內存可見緩存

執行控制的目的是控制代碼執行(順序)及是否能夠併發執行。安全

內存可見控制的是線程執行結果在內存中對其它線程的可見性。根據Java內存模型的實現,線程在具體執行時,會先拷貝主存數據到線程本地(CPU緩存),操做完成後再把結果從線程本地刷到主存。併發

volatile和synchronized的做用

synchronized關鍵字解決的是執行控制的問題,它會阻止其它線程獲取當前對象的監控鎖,這樣就使得當前對象中被synchronized關鍵字保護的代碼塊沒法被其它線程訪問,也就沒法併發執行。更重要的是,synchronized還會建立一個內存屏障,內存屏障指令保證了全部CPU操做結果都會直接刷到主存中,從而保證了操做的內存可見性,同時也使得先得到這個鎖的線程的全部操做,都happens-before於隨後得到這個鎖的線程的操做。app

volatile關鍵字解決的是內存可見性的問題,會使得全部對volatile變量的讀寫都會直接刷到主存,即保證了變量的可見性。這樣就能知足一些對變量可見性有要求而對讀取順序沒有要求的需求。高併發

使用volatile關鍵字僅能實現對原始變量(如boolen、 short 、int 、long等)操做的原子性,但須要特別注意, volatile不能保證複合操做的原子性,即便只是i++,實際上也是由多個原子操做組成:read i; inc; write i,假如多個線程同時執行i++volatile只能保證他們操做的i是同一塊內存,但依然可能出現寫入髒數據的狀況。性能

爲何volatile++不是原子性的

由於它其實是三個操做組成的一個複合操做。線程

  1. 首先獲取volatile變量的值
  2. 將該變量的值加1
  3. 將該volatile變量的值寫會到對應的主存地址

一個簡單的例子:code

若是兩個線程在volatile讀階段都拿到的是a=1,那麼後續在線程對應的CPU核心上進行自增固然都獲得的是a=2,最後兩個寫操做無論怎麼保證原子性,結果最終都是a=2。每一個操做自己都沒啥問題,可是合在一塊兒,從總體上看就是一個線程不安全的操做:發生了兩次自增操做,然而最終結果卻不是3。對象

第一步:讀

在第一步操做的指令後,會增長兩個內存屏障:排序

  1. 在Volatile讀操做後插入LoadLoad屏障,確保前面的數據的裝載先於全部後續裝載指令的裝載
  2. 在Volatile讀操做後插入LoadStore屏障,確保 前面的 數據裝載先於全部後續存儲指令刷新到內存

所以第一個指令和它後續的普通讀寫操做會被保證沒有重排序來搗亂。一般是去內存中去讀。

那麼問題又來了,爲何一般去內存中讀?

其實這個問題要說細的話能夠很細,大概就兩個關鍵點吧:

  1. volatile的寫操做的緩存失效機制
  2. 最後一個對volatile變量執行寫操做的CPU,因爲在它對應的緩存中保有最新的值,所以能夠不用再去主存裏面獲取

具體看下面第三步的分析。

第二步:自增

這個步驟沒什麼特別的,就是在CPU自身的高速緩存(寄存器,L1-L3 Cache)中完成。不涉及到緩存和內存的交互。

第三步:寫

volatile寫算是一個重點。

根據JMM對於volatile變量類型的語義規範:volatile在編譯以後,會在變量寫操做時添加LOCK前綴指令。這個LOCK前綴指令在多核處理器的環境中,有這樣的做用:

  1. 通知CPU將當前處理器緩存行的數據寫回到系統主存中
  2. 該寫回操做將使其餘CPU緩存了該內存地址的數據無效

另外,內存屏障在volatile的寫操做中起到了很大的做用,來保證上面兩點可以實現:

  1. 在Volatile寫操做前插入StoreStore屏障,確保前面的數據對其餘處理器可見(刷新到內存)先於續存儲指令的存儲
  2. 在Volatile寫操做後插入StoreLoad屏障,確保前面的數據對其餘處理器變得可見(指刷新到內存)前後續裝載指令的裝載。StoreLoad Barriers 會使該屏障以前的全部內存訪問指令(存儲和裝載指令)完成後,才執行該屏障以後的內存訪問指令

解決辦法

那麼爲了解決volatile++這類複合操做的原子性,有什麼方案呢?其實方案也比較多的,這裏提供兩種典型的:

  1. 使用synchronized關鍵字
  2. 使用AtomicInteger/AtomicLong原子類型

synchronized關鍵字

synchronized是比較原始的同步手段。它本質上是一個獨佔的,可重入的鎖。當一個線程嘗試獲取它的時候,可能會被阻塞住,因此高併發的場景下性能存在一些問題。

在某些場景下,使用synchronized關鍵字和volatile是等價的:

  1. 寫入變量值時候不依賴變量的當前值,或者可以保證只有一個線程修改變量值。
  2. 寫入的變量值不依賴其餘變量的參與。
  3. 讀取變量值時候不能由於其餘緣由進行加鎖。

加鎖能夠同時保證可見性和原子性,而volatile只保證變量值的可見性。

volatile和synchronized的區別總結

使用上的區別
volatile只能修飾變量,synchronized只能修飾方法和語句塊;

對原子性的保證
synchronized能夠保證原子性,volatile不能保證原子性;

對可見性的保證
均可以保證可見性,但實現原理不一樣,volatile對變量加了lock,synchronized使用monitorEnter和monitorExit;

對有序性的保證
均可以保證有序性,可是synchronized併發退化到串行;

其餘 synchronized引發阻塞;volatile不會引發阻塞;

相關文章
相關標籤/搜索