synchronized 是一個重量級的鎖, volatile 一般被比喻成輕量級的 synchronizedc++
volatile 是一個變量修飾符,只能用來修飾變量。數據庫
volatile寫:當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存。緩存
volatile讀:當讀一個volatile變量時,JMM會把該線程對應的本地內存置爲無效。線程接下來將從主內存中讀取共享變量。.net
volatile實現原理
1)JMM把內存屏障指令分爲下列四類:線程
StoreLoad Barriers是一個「全能型」的屏障,它同時具備其餘三個屏障的效果。現代的多處理器大都支持該屏障(其餘類型的屏障不必定被全部處理器支持)。執行該屏障開銷會很昂貴,由於當前處理器一般要把寫緩衝區中的數據所有刷新到內存中(buffer fully flush)。blog
Store:數據對其餘處理器可見(即:刷新到內存)排序
Load:讓緩存中的數據失效,從新從主內存加載數據 內存
2)JMM針對編譯器制定的volatile重排序規則表編譯器
是否能重排序 第二個操做
第一個操做 普通讀/寫 volatile讀 volatile寫
普通讀/寫 NO
volatile讀 NO NO NO
volatile寫 NO NO
舉例來講,第三行最後一個單元格的意思是:在程序順序中,當第一個操做爲普通變量的讀或寫時,若是第二個操做爲volatile寫,則編譯器不能重排序這兩個操做。編譯
從上表咱們能夠看出:
當第二個操做是volatile寫時,無論第一個操做是什麼,都不能重排序。這個規則確保volatile寫以前的操做不會被編譯器重排序到volatile寫以後。
當第一個操做是volatile讀時,無論第二個操做是什麼,都不能重排序。這個規則確保volatile讀以後的操做不會被編譯器重排序到volatile讀以前。
當第一個操做是volatile寫,第二個操做是volatile讀時,不能重排序。
JMM內存屏障插入策略(編譯器能夠根據具體狀況省略沒必要要的屏障):
在每一個volatile寫操做的前面插入一個StoreStore屏障。
對於這樣的語句Store1; StoreStore ; Store2,在Store2及後續寫入操做執行前,保證Store1的寫入操做對其它處理器可見。
在每一個volatile寫操做的後面插入一個StoreLoad屏障。
對於這樣的語句Store1; StoreLoad ; Load2,在Load2及後續全部讀取操做執行前,保證Store1的寫入對全部處理器可見。
在每一個volatile讀操做的後面插入一個LoadLoad屏障。
對於這樣的語句Load1; LoadLoad ; Load2,在Load2及後續讀取操做要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。
在每一個volatile讀操做的後面插入一個LoadStore屏障。
對於這樣的語句Load1; LoadStore ; Store2,在Store2及後續寫入操做被刷出前,保證Load1要讀取的數據被讀取完畢。
volatile保證可見性
volatile修飾的變量寫以後將本地內存刷新到主內存,保證了可見性
volatile保證有序性
volatile變量讀寫先後插入內存屏障以免重排序,保證了有序性
volatile不保證原子性
volatile不是鎖,與原子性無關
要我說,因爲CPU按照時間片來進行線程調度的,只要是包含多個步驟的操做的執行,自然就是沒法保證原子性的。由於這種線程執行,又不像數據庫同樣能夠回滾。若是一個線程要執行的步驟有5步,執行完3步就失去了CPU了,失去後就可能不再會被調度,這怎麼可能保證原子性呢。
爲何 synchronized 能夠保證原子性 ,由於被 synchronized 修飾的代碼片斷,在進入以前加了鎖,只要他沒執行完,其餘線程是沒法得到鎖執行這段代碼片斷的,就能夠保證他內部的代碼能夠所有被執行。進而保證原子性。(摘自http://www.hollischuang.com/archives/2673)
volatile不保證原子性的例子:
/**
* 建立10個線程,而後分別執行1000次i++操做。目的是程序輸出結果10000
* 可是,屢次執行的結果都小於10000。這其實就是volatile沒法知足原子性的緣由。
*/
public class Test {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for (int i = 0; i < 10; i++) {
new Thread() {
public void run() {
for (int j = 0; j < 1000; j++)
test.increase();
};
}.start();
}
while (Thread.activeCount() > 1) // 保證前面的線程都執行完 Thread.yield(); System.out.println(test.inc); } } --------------------- 做者:qq_43171869 來源:CSDN 原文:https://blog.csdn.net/qq_43171869/article/details/83660440 版權聲明:本文爲博主原創文章,轉載請附上博文連接!