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