volatile變量與普通變量的區別

咱們一般會用volatile實現一些須要線程安全的代碼(也有不少人不敢用,由於不瞭解),但事實上volatile自己並非線程安全的,相對於synchoronized,它有更多的使用侷限性,只能限制在某些特定的場景。本篇文章的目的就是讓你們對 volatile 在本質上有個把握,爲了達到這個目的,咱們會從java 的內存模型及變量操做的內存管理來講明(不用怕,你會發現很簡單)。html

1、內存模型

能夠將內存簡單分爲兩種:工做內存和主內存。全部的數據最終都須要存儲在主內存,工做內存是線程獨有的,線程之間無任何干擾。java的內存模型主要就是定義工做內存和主內存的交互,即工做內存如何從主內存拷貝數據,以入如何寫數據。java 定義了8種原子性操做來完成工做內存與主內存的交互:java

  • lock 將對象變成線程獨佔的狀態
  • unlock 將線程獨佔狀態的對象的鎖釋放出來
  • read 從主內存讀數據
  • load 將從主內存讀取的數據寫入工做內存
  • use 工做內存使用對象
  • assign 對工做內存中的對象進行賦值
  • store 將工做內存中的對象傳送到主內存當中
  • write 將對象寫入主內存當中,並覆蓋舊值

這些操做也是有必定的條件限制的:
read 和load,store和write 必須成對出現,即從主內存中讀數據的數據工做內存必須接受;傳遞到主內存的數據,也不能夠被拒絕寫入。
assign後的對象必須回寫到緩存
未進行新賦值的對象不容許回寫到主內存
新的變量只能在主內存產生,且未完成初始化的對象不容許在工做內存中使用
對象只容許被一條線程鎖定,且能夠被此線程屢次鎖定
未被鎖定的對象不容許執行unlock操做
對一個對象執行unlock以前,必須將對象回寫到主內存
java的8種原子性操做,相互以前有必定的約束條件,但並無嚴格限制任意兩個操做必須連續出現,只是表示成對出現,這也是爲何會產生線程不安全性的緣由。
介紹了上述的背景知識,那咱們就來看一下volatile變量到底和普通變量有啥差異吧緩存

2、volatile變量與普通變量

2.1 volatile 的安全性

下面咱們用一個例子來講明volatile變量與普通變量的區別。
假設有兩個線程操做一個主內存的對象,且線程1早於線程2開始(以下例如示一個a++操做))安全

public class ThreadSafeTest {
    public static int a = 0;

    public static void increase() {
        a++;
    }


    public static void main (String[] args) {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int j = 0; j < 100; j++) {
                    increase();
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int j = 0; j < 100; j++) {
                    increase();
                }
            }
        });

        t1.start();
        t2.start();
    }
}複製代碼

線程2讀取主內存對象(a)時,可能發生在幾個時期:read以前、read以後、load以後、use以後、assign以後、 store以後、write以後(以下圖所示);bash

假設線程1執行了a++,a從0變成了1,還將來得及寫回主內存對象,線程2從主內存對象中讀取的數據a=0;此時線程1寫入主內存a=1,而線程2仍然執行完了a++ ,此時仍然 等於1(應該等於2),實際上,這就上至關於線程2 讀入了一個過時的數據,致使線程不安全。ide

那若是將a變成volatile對象是否就正確了呢?
volatile對對象的操做作了更嚴格的限制:性能

  • use以前不進行read和load
  • assign以後必須緊跟store和write
    實際至關於將read load use 三個原子操做變成一個原子操做;將assign-store-write變成一個原子操做。不少文章上都講volatile對全部的線程是可見的,指的就是執行完了assign以後當即就會回寫主內存;在任意一個線程讀取主內存對象時,都會刷新主內存。在主內存中表現是數據一致性的,可是各線程內存當中卻不必定是一致性的。
    一樣是上面的代碼,換成volatile
    ```
    public class ThreadSafeTest {
    public static volatile int a = 0;spa

    public static void increase() {線程

    a++;複製代碼

    }3d

public static void main (String[] args) {

    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int j = 0; j < 100; j++) {
                increase();
            }
        }
    });

    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int j = 0; j < 100; j++) {
                increase();
            }
        }
    });

    t1.start();
    t2.start();

}複製代碼

}

```
運行後發現,也拿不到正確的結果(若是拿到請把j的數值調大)。操你媽,不是說是線程安全的變量嗎?爲啥也不正確?
這是由於線程內部的數據仍然有可能存在不一致性,好比,若是線程2讀取數據時,處在線程1use以後,但線程1此時還將來得及回寫主緩存,這時候線程2使用到的數據仍然是0,兩個線程同時對0++,獲得的結果只會是1,而不是理想中的2。

2.2 volatile 的線程安全是有條件的

即然volatile 是非線程安全的,那要它還有什麼用呢?若是你看過我寫過的「線程安全」的文章應該知道,全部的對象都是相對線程安全的,也就是有條件的。volatile的線程安全固然也是有條件的,它是對synchronized這一重量級線程同步的一種補充,其總體性能上優於synchronized。那volatile的線程安全的條件是什麼呢?適合使用在哪些場景?
《java虛擬機》給出兩個條件:

  • 運算結果並不依賴變量的當前值(即結果對產生中間結果不依賴),或者可以確保只有單一的線程修改變量的值
  • 變量不須要與其它的狀態變量共同參與不變約束(我認爲此條畫蛇添足,這個其它變量也必須得是線程安全的才行)

那適合哪些場景呢?這個我就不一一舉例了,一個哥門總結得很好,參考以下:www.ibm.com/developerwo…

相關文章
相關標籤/搜索