Volatile和Synchronized

線程安全可以概括爲三個方面:原子性可見性有序性

原子性:對於涉及共享變量的操作看做一個整體,在同一時間內,只能由一個線程執行,在其它線程看來,這部分操作要麼尚未開始,要麼已經完成。Java中,基本類型除了long和double,其它類型變量的寫操作都是原子性的。

可見性:一個線程修改了共享變量後,其它線程能夠立即看見改變後的值。

有序性:即程序按照代碼的先後順序執行。我們寫好的代碼在執行的時候不一定是按照順序的,因爲虛擬機編譯的時候,在保證輸出結果不變的情況下可能對代碼進行優化,也就是常說的指令重排。在單線程的情況下不會有什麼影響,但是多線程的環境下則會有隱患。

Volatile

先來看Java內存模型

 線程先從主內存讀取到變量,對變量進行修改之後再刷新回主內存。當使用了volatile之後:線程直接讀寫主內存。

這就保證了共享變量在線程間的可見性。一個常見的例子就是使用volatile修飾的變量,線程A通過改變變量的值去停止另一個正在運行的線程B。

class MyTask implements Runnable{

    private volatile boolean flag = true;

    public void stop(){
        flag = false;
    }

    @Override
    public void run() {
        System.out.println("====進入循環====" + flag);
        while (flag){
        }
        System.out.println("====停止循環====" + flag);
    }
}

測試

import java.util.concurrent.*;

public class Main {
    
    public static void main(String[] args) throws Exception {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 15, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));

        MyTask task = new MyTask();
        executor.execute(task);
        Thread.sleep(2000);
        System.out.println("外部調用stop");
        task.stop();
    }

}

如果不加volatile關鍵字,則不能停止線程。因爲其它線程不能立即看見改變。另外volatile也保證了有序性

對於volatile變量的寫操作,JVM會在該操作之前加入一個釋放屏障,操作之後加入一個存儲屏障

 

 釋放屏障禁止了volatile寫操作和該操作之前的任何讀寫操作進行重排序。這就保障了實際執行順序和源代碼順序一樣,即保障了有序性。

volatile雖然能夠保障有序性,但是不具有鎖那樣的排他性,只能夠保證所修飾變量寫操作的原子性,不能保證其他操作的原子性。

對於volatile的讀操作,JVM會在該操作之前加入一個加載屏障,操作之後加入一個獲取屏障

 

 volatile總結:

volatile變量的寫操作與該操作之的任何讀寫操作不會被重排序。

volatile變量的讀操作與該操作之的任何讀寫操作不會被重排序。

volatile只能保證可見性和有序性,對於包含多個操作的共享區域,不能保證線程安全。

Synchronized

先來個簡單代碼

package com.demo.tools;


public class Demo {

    public void hello(){
        synchronized(this) {
            System.out.println("Hello");
        }
    }

}

編譯之後進入classes目錄,查看編譯後的結構。

 

可以看到在代碼中用synchronized包起來的代碼塊前後有兩個東西:monitorentermonitorexit,這就涉及了一個叫做Monitor的東西:我們知道對象在內存中分爲三個區域(對象頭、實例變量,填充數據),而這個Monitor則存儲在對象頭裏。

當執行到monitorenter,線程嘗試獲取鎖(也就是搶佔Monitor);也因爲Monitor存在對象頭裏,所以解釋了爲什麼Java中任意對象都可以作爲鎖。其中還有個計數器,當爲0的時候代表可以獲取,當線程獲取到了,計數器+1,當執行到monitorexit的時候,線程不佔有這個鎖,計數器-1。由於Synchronized是可重入鎖,也就是在持有當前鎖的基礎上繼續獲取當前鎖,是可以的,這個時候,計數器繼續+1,退出同步區域則-1,直到爲0。

同時Synchronized還維護了一個入口集(Entry Set),這個集合存放等待的線程。

Volatile和Synchronized區別

volatile只能修飾變量,synchronized可以修飾方法和代碼塊。
多線程訪問volatile不會阻塞,synchronized會阻塞。
volatile只保證了變量在多個線程之間的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因爲它會將工作內存和主內存中的數據做同步處理。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章