1. 程式人生 > 程式設計 >java Volatile與Synchronized的區別

java Volatile與Synchronized的區別

引言

在研究併發程式時,我們可能都知道volatile和synchronized是用於多執行緒中,用於執行緒安全和變數可見性的,但是具體兩者怎麼使用,有何區別可能還是稀裡糊塗一知半解,在此就自己簡單的理解總結一下二者的區別,和大家一塊兒學習!我們需要了解java中關鍵字volatile和synchronized關鍵字的使用以及lock類的用法。

首先,瞭解下java的記憶體模型:

java Volatile與Synchronized的區別

java的執行緒記憶體模型中定義了每個執行緒都有一份自己的共享變數副本(本地記憶體),裡面存放自己私有的資料,其他執行緒不能直接訪問,而一些共享變數則存在主記憶體中,供所有執行緒訪問。
上圖中,如果執行緒A和執行緒B要進行通訊,就要經過主記憶體,比如執行緒B要獲取執行緒A修改後的共享變數的值,要經過下面兩步:

(1)、執行緒A修改自己的共享變數副本,並重新整理到了主記憶體中。
(2)、執行緒B讀取主記憶體中被A更新過的共享變數的值,同步到自己的共享變數副本中。
總結:在java記憶體模型中,共享變數存放在主記憶體中,每個執行緒都有自己的本地記憶體,當多個執行緒同時訪問一個數據的時候,可能本地記憶體沒有及時重新整理到主記憶體,所以就會發生執行緒安全問題。

java多執行緒中的三個特性:

  •   原子性:即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。一個很經典的例子就是銀行賬戶轉賬問題:比如從賬戶A向賬戶B轉1000元,那麼必然包括2個操作:從賬戶A減去1000元,往賬戶B加上1000元。這2個操作必須要具備原子性才能保證不出現一些意外的問題。
  •   可見性:當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值。
  •   有序性:就是程式執行的順序按照程式碼的先後順序執行。一般來說處理器為了提高程式執行效率,可能會對輸入程式碼進行優化,它不保證程式中各個語句的執行先後順序同程式碼中的順序一致,但是它會保證程式最終執行結果和程式碼順序執行的結果是一致的。如下:
int a = 20; //語句1
int r = 3; //語句2
a = a + 5; //語句3
r = a*a; //語句4

因為重排序,他的執行順序還可能為 2 -1 - 3 - 4,1 - 3 - 2 - 4。但絕不可能 2 -1 - 4 - 3,因為這打破了依賴關係。顯然重排序對單執行緒執行是不會有任何問題,而多執行緒就不一定了,所以我們在多執行緒程式設計時就得考慮這個問題了。

Volatile關鍵字的作用:

其實volatile關鍵字的作用就是保證了可見性和有序性(不保證原子性),如果一個共享變數被volatile關鍵字修飾,那麼如果一個執行緒修改了這個共享變數後,其他執行緒是立馬可知的。如果執行緒A修改了自己的共享變數副本,這時如果該共享變數沒有被volatile修飾,那麼本次修改不一定會馬上將修改結果重新整理到主存中,如果此時B去主存中讀取共享變數的值,那麼這個值就是沒有被A修改之前的值。如果該共享變數被volatile修飾了,那麼本次修改結果會強制立刻重新整理到主存中,如果此時B去主存中讀取共享變數的值,那麼這個值就是被A修改之後的值了。
  volatile禁止指令重排序優化,在指令重排序優化時,在volatile變數之前的指令不能在volatile之後執行,在volatile之後的指令也不能在volatile之前執行,所以它保證了有序性。
  volatile 的讀效能消耗與普通變數幾乎相同,但是寫操作稍慢,因為它需要在原生代碼中插入許多記憶體屏障指令(是一種CPU指令,用於控制特定條件下的重排序和記憶體可見性問題。Java編譯器也會根據記憶體屏障的規則禁止重排序。)來保證處理器不發生亂序執行。

Synchronized關鍵字的作用:

synchronized提供了同步鎖的概念,被synchronized修飾的程式碼段可以防止被多個執行緒同時執行,必須一個執行緒把synchronized修飾的程式碼段都執行完畢了,其他的執行緒才能開始執行這段程式碼。 因為synchronized保證了在同一時刻,只能有一個執行緒執行同步程式碼塊,所以執行同步程式碼塊的時候相當於是單執行緒操作了,那麼執行緒的可見性、原子性、有序性(執行緒之間的執行順序)它都能保證了。synchronized並沒有禁止重排序,但是synchronized相當於是一個單執行緒了,所以有沒有重排序對程式都是沒有影響的。

Volatile和synchronized的區別: 

  (1)、volatile只能作用於變數,使用範圍較小。synchronized可以用在變數、方法、類、同步程式碼塊等,使用範圍比較廣。
  (2)、volatile只能保證可見性和有序性,不能保證原子性。而可見性、有序性、原子性synchronized都可以包證。
  (3)、volatile不會造成執行緒阻塞。synchronized可能會造成執行緒阻塞。
  (4)、在效能方面synchronized關鍵字是防止多個執行緒同時執行一段程式碼,就會影響程式執行效率,而volatile關鍵字在某些情況下效能要優於synchronized。

什麼是重排序:

  重排序是指編譯器和處理器為了優化程式效能而對指令序列進行重新排序的一種手段。但是重排序可以保證最終執行的結果是與程式順序執行的結果一致,並且只會對不存在資料依賴性的指令進行重排序,這個重排序在單執行緒下對最終執行結果是沒有影響的,但是在多執行緒下就會存在問題。

可以看一個例子: 

class TestExample {
  int a = 0;
  boolean flag = false;
 
 // 寫入的執行緒
 public void writer() {
  a = 1; // 1
  flag = true; // 2
 
 } //讀取的執行緒
public void reader() {
 if (flag) { // 3
 int i = a * a; // 4 
 }}}

如上面程式碼,如果兩個執行緒同時執行在沒有發生重排序的時候int i =1,如果發生了重排序那麼1,2的位置因為不存在資料依賴可以會發生位置的互換。那麼這時候int i =0;當然這個在單執行緒是沒有問題的。只有在多執行緒才會發生這種情況

 volatile int a = 0;
 volatile boolean flag = false;

我們只需要加上volatile關鍵字也是可以避免這種問題的,volatile是禁止重排序的。

什麼是資料依賴?

 int a = 1;(1)
 int b = 2;(2)
 int c= a + b;(3)

這裡面第三步就存在資料依賴 (依賴a和b的值)。編譯器和處理器在重排序時,會遵守資料依賴性,編譯器和處理器不會改變存在資料依賴關係的兩個操作的執行順序。所以這裡面無論步驟(1)(2)有沒有發生重排序,步驟(3)都是在他們之後執行。這裡所說的資料依賴性僅針對單個處理器中執行的指令序列和單個執行緒中執行的操作,不同處理器之間和不同執行緒之間的資料依賴性不被編譯器和處理器考慮。

以上就是java Volatile與Synchronized的區別的詳細內容,更多關於java Volatile與Synchronized區別的資料請關注我們其它相關文章!