volatile 關鍵字
1. 前言
本節內容主要是對 volatile 關鍵字進行講解,具體內容點如下:
- volatile 關鍵字概念介紹,從整體層面了解 volatile 關鍵字;
- volatile 關鍵字與 synchronized 關鍵字的區別,這是本節的重點內容之一,了解 volatile 關鍵字與 synchronized 關鍵字的區別,才能更好地區分并掌握兩鐘關鍵字的使用;
- volatile 關鍵字原理介紹,也是本節課程的重點之一;
- volatile 關鍵字的使用,是本節課程的核心內容,所有的知識點都是圍繞這一目的進行講解的。
2. volatile 關鍵字介紹
概念:volatile 關鍵字解決內存可見性問題,是一種弱形式的同步。
介紹:該關鍵字可以確保當一個線程更新共享變量時,更新操作對其他線程馬上可見。當一個變量被聲明為 volatile 時,線程在寫入變量時不會把值緩存在寄存器或者其他地方,而是會把值刷新回主內存。
當其他線程讀取該共享變量時,會從主內存重新獲取最新值,而不是使用當前線程的工作內存中的值。
3. volatile 與 synchronized 的區別
相似處:volatile 的內存語義和 synchronized 有相似之處,具體來說就是,當線程寫入了 volatile 變量值時就等價于線程退出 synchronized 同步塊(把寫入工作內存的變量值同步到主內存),讀取 volatile 變量值時就相當于進入 synchronized 同步塊( 先清空本地內存變量值,再從主內存獲取最新值)。
區別:使用鎖的方式可以解決共享變量內存可見性問題,但是使用鎖太笨重,因為它會帶來線程上下文的切換開銷。具體區別如下:
- volatile 本質是在告訴 jvm 當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀??;synchronized 則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞??;
- volatile 僅能使用在變量級別;synchronized 則可以使用在變量、方法、和類級別的;
- volatile 僅能實現變量的修改可見性,不能保證原子性;而 synchronized 則可以保證變量的修改可見性和原子性;
- volatile 不會造成線程的阻塞;synchronized 可能會造成線程的阻塞;
- volatile 標記的變量不會被編譯器優化;synchronized 標記的變量可以被編譯器優化
4. volatile 原理
原理介紹:Java 語言提供了一種弱同步機制,即 volatile 變量,用來確保將變量的更新操作通知到其他線程。
當把變量聲明為 volatile 類型后,編譯器與運行時都會注意到這個變量是共享的,volatile 變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取 volatile 類型的變量時總會返回最新寫入的值。
Tips:在訪問 volatile 變量時不會執行加鎖操作,因此也就不會使執行線程阻塞,因此 volatile 變量是一種比 sychronized 關鍵字更輕量級的同步機制。
我們來通過下圖對非 volatile 關鍵字修飾的普通變量的讀取方式進行理解,從而更加細致的了解 volatile 關鍵字修飾的變量。
當對非 volatile 變量進行讀寫的時候,每個線程先從內存拷貝變量到 CPU 緩存中。如果計算機有多個 CPU,每個線程可能在不同的 CPU 上被處理,這意味著每個線程可以拷貝到不同的 CPU cache 中。
而聲明變量是 volatile 的,JVM 保證了每次讀變量都從內存中讀,跳過 CPU cache。
5. volatile 關鍵字的使用
為了對 volatile 關鍵字有著更深的使用理解,我們通過一個非常簡單的場景的設計來進行學習。
場景設計:
- 創建一個 Student 類,該類有一個 String 屬性,name;
- 將 name 的 get 和 set 方法設置為同步方法;
- 使用 synchronized 關鍵字實現;
- 使用 volatile 關鍵字實現。
這是一個非常簡單的場景,場景中只涉及到了一個類的兩個同步方法,通過對兩種關鍵字的實現,能更好的理解 volatile 關鍵字的使用。
實例: synchronized 關鍵字實現
public class Student {
private String name;
public synchronized String getName() {
return name;
}
public synchronized void setName(String name) {
this.name = name;
}
}
實例: volatile 關鍵字實現
public class Student {
private volatile String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
總結:在這里使用 synchronized 和使用 volatile 是等價的,都解決了共享變量 name 的內存可見性問題。
但是前者是獨占鎖,同時只能有一個線程調用 get()方法,其他調用線程會被阻塞,同時會存在線程上下文切換和線程重新調度的開銷,這也是使用鎖方式不好的地方。
而后者是非阻塞算法,不會造成線程上下文切換的開銷。
6. 小結
本節內容的核心知識點即 volatile 關鍵字的使用方式,在掌握核心知識之前,需要對重點內容進行理解和學習,本節內容所有的重點知識如 volatile 關鍵字原理,與 synchronized 關鍵字的區別,都是圍繞核心知識進行的講解。