1. 程式人生 > 其它 >學習筆記【多執行緒-第八節:volatile個人詳解】

學習筆記【多執行緒-第八節:volatile個人詳解】

技術標籤:筆記java多執行緒併發程式設計

其實說volatile,個人覺得比synchronized簡單多了,他並不是一個鎖,只是一個可以修飾的關鍵字,作用就兩個:可見性與禁止指令重排序。

可見性與禁止指令重排序

我們先來看一個簡單的例子:

public class NowTest {
    private static boolean temp=true;
    public static void main(String[] args) {
        new Thread(()->{
            while(temp){}
            System.
out.println("end!"); }).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } temp=false; } }

兩個執行緒都訪問到了temp,主執行緒也已經將其改為了false,但結果是程式一直無法停止,這是因為他們之間並沒有可見性,主執行緒雖然將值改了並傳回了主記憶體,但另一個執行緒得不到。如果給temp加一個volatile呢?

在這裡插入圖片描述
程式竟然停止了,這是為什麼呢,其實我們在將JMM時講過嗅探機制,這裡也差不多。

我們先看看volatile底層是如何實現的
Java程式碼:volatile
位元組碼:VCC_VOLATILE
JVM層面:記憶體屏障
hotspot層面:
在這裡插入圖片描述
在這裡插入圖片描述
這裡會判斷是否有volatile修飾,下面呼叫了OrderAccess::fence()方法(fence籬笆),該方法中用了lock;add1 。

也就是說,其實volatile和synchronized一樣,都是用了lock,這裡回顧一下lock的作用:
在多處理器中執行指令時對共享記憶體獨佔使用;
能夠立即將當前處理器對應的快取的內容重新整理到記憶體,並使其他處理器對應的快取失效;

提供了有序指令無法超過這個記憶體屏障的作用。

還有JVM層級的記憶體屏障,其實在happen-before的具體規則中就有一項說到:
volatile變數規則:對一個volatile域的寫,happens-before於任意後續對這個volatile域的讀。
先不說這是什麼意思,先看看記憶體屏障,記憶體屏障分為lfence(Load屏障)、sfence(Store屏障)、mfence(兩者都有),volatile的記憶體屏障:
在這裡插入圖片描述

不具備原子性

我剛學的時候有一個誤區,那就是總是拿synchronized和volatile作比較,如今的我是這麼認為的,他們根本就不能放在一起說,雖然有相似的同步道理,但一個是鎖,一個不是鎖,有什麼好比的呢。

volatile不是鎖,雖然他有lock指令,有禁止指令重排,但也無法讓其具備原子性,舉個例子:兩個執行緒對同一個a做a++的操作(①拿到 a ②計算 a + 1得出結果③將結果賦值給 a), 倘若執行緒A執行緒B 都完成了②的操作,但執行緒A先完成store操作,可見性讓 執行緒B 知道a被改了,就會失效a值,重新回主記憶體中取值,但 執行緒B 已經進行完第二步,即便取了新的a值也會直接進行第三步,最終,導致 執行緒B 這次的操作作廢。

我聽過有句話說的挺好:

不要將volatile用在getAndOperate場合(這種場合不原子,需要再加鎖),僅僅set或者get的場景是適合volatile的。就好比CAS

其實,DCL單例就已經概括了volatile的這幾個特點,關於DCL單例要不要加volatile,那一定是要的。