Java併發:volatile的實現原理

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  版權聲明:本文爲博主原創文章,轉載請附上博文連接!

相關文章
相關標籤/搜索