關於volatile

勤儉的搬運工發表於2020-12-24

1.volatile

volatile是java虛擬機器提供的輕量級同步機制

2.特性

保證可見性,不保證原子性,禁止指令重排(有序性)

2.1 可見性

首先要知道JMM,就是java記憶體模型(可見性、原子性、有序性)
這是一個抽象概念;記憶體分為主記憶體和工作記憶體。主記憶體主要存放共享變數等等,用於資料共享的
而工作記憶體是執行緒操作資源的一個區域,每個執行緒都有自己的工作記憶體。
資源的操作流程主要分為以下三步:

  1.執行緒從主記憶體中copy取出需要操作的資源  
  2.執行緒操作資源  
  3.寫回主記憶體(在CAS中,需要比較此時主記憶體的內容和之前拿到的內容是否相同)  

而線上程寫回主記憶體後,需要通知其他執行緒這個共享變數已經被修改,如果修改了,其他執行緒就重
新從主記憶體中獲取,這就叫做可見性

下面是一段程式碼演示:

/**
 * volatile的可見性
 */
public class TestVolatile {
    public static void main(String[] args) {
        SourceTest sourceTest = new SourceTest();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {e.printStackTrace();}
            sourceTest.addNum();
            System.out.println(Thread.currentThread().getName() + "修改a後: " + sourceTest.a);
        },"sub thread").start();

        while (sourceTest.a == 0){
            //進入迴圈
            System.out.println("主執行緒進入迴圈取得a的值:" + sourceTest.a);
        }

        System.out.println(Thread.currentThread().getName() + "得到a的值: " + sourceTest.a);
    }
}

class SourceTest{
    //int a = 0;//此時不加volatile主執行緒進入死迴圈,程式無法結束
    volatile int a = 0;

    void addNum(){
        a = 60;
    }
}

輸出:
...
主執行緒進入迴圈取得a的值:0
主執行緒進入迴圈取得a的值:0
sub thread修改a後: 60
main得到a的值: 60

2.2 不保證原子性

原子性:不可分割,完整性,即執行緒在處理某個業務時,中間不允許被加塞

public class TestAtomic {
    public static void main(String[] args) throws InterruptedException {
        DataSource dataSource = new DataSource();

        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000 ; j++) {
                    dataSource.getAndIncre();
                }
            },String.valueOf(i)).start();
        }

        //等待以上執行緒執行完成,main執行緒取值
        //TimeUnit.SECONDS.sleep(5);
        while (Thread.activeCount() > 2){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + "\t 最終number: " + dataSource.number);
    }
}

class DataSource{
    volatile int number = 0;

    void getAndIncre(){
        number++;
    }
}

輸出結果:
main 最終number: 19081

以此可看出volatile不保證原子性(i++執行緒不安全=>主要是i++先自增,再返回自增之前的值,導致某時通知判斷錯誤)

解決無法保證原子性

1.加sync
2.AtomicInteger

AtomicInteger atomicInteger = new AtomicInteger();

    void getAndIncre(){
        //number++;
        atomicInteger.getAndIncrement();
    }

2.3 禁止指令重排(有序性)

計算機在執行程式時,為了提高效能,編譯器和處理器會對指令進行重排:
原始碼>(編譯器優化的重排>指令並行的重排>記憶體系統的重排)>最終執行的指令
在單執行緒環境中,可以確保最終執行的結果與程式碼順序一致
處理器在重排時必須考慮指令之間的資料依賴性
而多執行緒環境中執行緒交替執行,兩個執行緒使用的變數就無法確定能保持一致性

相關文章