Volatile與Synchronized的區別

2022-09-16 00:12:24 字數 3295 閱讀 4703

j**a執行緒的記憶體模型

j**a的執行緒記憶體模型中定義了每個執行緒都有乙份自己的共享變數副本(本地記憶體),裡面存放自己私有的資料,其他執行緒不能直接訪問,而一些共享變數則存在主記憶體中,供所有執行緒訪問。

上圖中,如果執行緒a和執行緒b要進行通訊,就要經過主記憶體,比如執行緒b要獲取執行緒a修改後的共享變數的值,要經過下面兩步:

(1)、執行緒a修改自己的共享變數副本,並重新整理到了主記憶體中。

(2)、執行緒b讀取主記憶體中被a更新過的共享變數的值,同步到自己的共享變數副本中。

總結:在j**a記憶體模型中,共享變數存放在主記憶體中,每個執行緒都有自己的本地記憶體,當多個執行緒同時訪問乙個資料的時候,可能本地記憶體沒有及時重新整理到主記憶體,所以就會發生執行緒安全問題。

j**a多執行緒中的三個特性:

原子性:即乙個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。乙個很經典的例子就是銀行賬戶轉賬問題:比如從賬戶a向賬戶b轉1000元,那麼必然包括2個操作:從賬戶a減去1000元,往賬戶b加上1000元。這2個操作必須要具備原子性才能保證不出現一些意外的問題。

可見性:當多個執行緒訪問同乙個變數時,乙個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值。

有序性:就是程式執行的順序按照**的先後順序執行。一般來說處理器為了提高程式執行效率,可能會對輸入**進行優化,它不保證程式中各個語句的執行先後順序同**中的順序一致,但是它會保證程式最終執行結果和**順序執行的結果是一致的。如下:

int a = 10; //語句1

int r = 2; //語句2

a = a + 3; //語句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指令,用於控制特定條件下的重排序和記憶體可見性問題。j**a編譯器也會根據記憶體屏障的規則禁止重排序。)來保證處理器不發生亂序執行。

synchronized關鍵字的作用:  

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

volatile和synchronized的區別: 

(1)、volatile只能作用於變數,使用範圍較小。synchronized可以用在變數、方法、類、同步**塊等,使用範圍比較廣。

(2)、volatile只能保證可見性和有序性,不能保證原子性。而可見性、有序性、原子性synchronized都可以包證。

(3)、volatile不會造成執行緒阻塞。synchronized可能會造成執行緒阻塞。

(4)、在效能方面synchronized關鍵字是防止多個執行緒同時執行一段**,就會影響程式執行效率,而volatile關鍵字在某些情況下效能要優於synchronized。

什麼是重排序

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

可以看乙個例子: 

class reorderexample // 讀取的執行緒

public void reader() }}

如上面**,如果兩個執行緒同時執行在沒有發生重排序的時候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)

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

s-if-serial語義

無論怎麼排序(編譯器和處理器為了提高並行度),(單執行緒)程式的執行結果不能被改變。編譯器,runtime 和處理器都必須遵守as-if-serial語義。

為了遵守as-if-serial語義,編譯器和處理器不會對存在資料依賴關係的操作做重排序。

volatile與synchronized的區別

volatile本質是在告訴jvm當前變數在暫存器中的值是不確定的,需要從主存中讀取,synchronized則是鎖定當前變數,只有當前執行緒可以訪問該變數,其他執行緒被阻塞住.volatile僅能使用在變數級別,synchronized則可以使用在變數,方法.volatile僅能實現變數的修改可見...

volatile原理與技巧

為什麼使用 volatile 比同步代價更低?同步的代價,主要由其覆蓋範圍決定,如果可以降低同步的覆蓋範圍,則可以大幅提公升程式效能。而volatile 的覆蓋範圍僅僅變數級別的。因此它的同步代價很低。volatile 原理是什麼?volatile 的語義,其實是告訴處理器,不要將我放入工作記憶體,...

重排序與volatile

為了提高編譯器和處理器的能力,對 編譯執行順序進行修改 a和b沒有依賴關係,編譯時可能會是b先執行在執行a int a 1 int b 2 下面由於b依賴於a,所以不會進行重排序 int a 1 int b a 1當乙個執行緒對共享變數進行修改,其他執行緒可以立即知道新的共享變數的值,防止重排序 每...