Java中volatile關(guān)鍵字的作用與線程安全分析

volatile關(guān)鍵字在Java中主要用于保證變量的可見性和禁止指令重排序,但不能保證原子性。1.可見性:當(dāng)一個(gè)線程修改了volatile變量的值,其他線程可以立即得知該新值,因?yàn)槊看巫x取和寫入都直接與主內(nèi)存交互;2.禁止指令重排序:通過插入內(nèi)存屏障防止jvm優(yōu)化時(shí)改變指令順序,從而避免多線程環(huán)境下的意外行為;3.不保證原子性:對(duì)于如i++這樣的復(fù)合操作,volatile無法確保線程安全,此時(shí)仍需使用鎖機(jī)制;4.適用場(chǎng)景:適用于一個(gè)線程寫、多個(gè)線程讀的情況,例如狀態(tài)標(biāo)記或單例模式中的雙重檢查鎖定;5.誤用場(chǎng)景:不應(yīng)用于涉及多個(gè)線程同時(shí)寫入、復(fù)合操作或依賴當(dāng)前值的操作;6.與synchronized的區(qū)別:volatile不提供原子性和鎖機(jī)制,開銷較小,適合簡(jiǎn)單狀態(tài)同步,而synchronized能保障原子性、可見性和有序性,適合需要互斥訪問的復(fù)雜并發(fā)控制。

Java中volatile關(guān)鍵字的作用與線程安全分析

volatile 關(guān)鍵字在 Java 中主要用于保證變量的可見性和禁止指令重排序,但它并不能保證原子性,因此不能完全解決線程安全問題。它適用于一個(gè)線程寫,多個(gè)線程讀的場(chǎng)景。

Java中volatile關(guān)鍵字的作用與線程安全分析

解決方案

volatile 關(guān)鍵字的作用在于:

Java中volatile關(guān)鍵字的作用與線程安全分析

  1. 可見性: 當(dāng)一個(gè)線程修改了被 volatile 修飾的變量的值,這個(gè)新值對(duì)于其他線程來說是可以立即得知的。 這是通過在每次讀取變量時(shí)都從主內(nèi)存中讀取,每次寫入變量后都立即寫回主內(nèi)存來實(shí)現(xiàn)的。

    立即學(xué)習(xí)Java免費(fèi)學(xué)習(xí)筆記(深入)”;

    Java中volatile關(guān)鍵字的作用與線程安全分析

  2. 禁止指令重排序: volatile 可以防止 JVM 對(duì)其修飾的變量進(jìn)行指令重排序優(yōu)化,從而避免一些意想不到的并發(fā)問題。指令重排序是指 JVM 為了優(yōu)化程序的執(zhí)行效率,在不影響單線程程序語義的情況下,可以重新安排指令的執(zhí)行順序。但在多線程環(huán)境下,指令重排序可能會(huì)導(dǎo)致程序出現(xiàn)錯(cuò)誤。volatile 通過插入內(nèi)存屏障來禁止指令重排序。

然而,volatile 并不能保證原子性。原子性是指一個(gè)操作是不可中斷的,要么全部執(zhí)行成功,要么全部執(zhí)行失敗。對(duì)于復(fù)合操作(例如 i++),volatile 是無能為力的。

舉個(gè)例子:

public class VolatileExample {     private volatile int counter = 0;      public void increment() {         counter++; // 這是一個(gè)復(fù)合操作:讀取-修改-寫入     }      public int getCounter() {         return counter;     } }

即使 counter 被 volatile 修飾,increment() 方法仍然不是線程安全的。多個(gè)線程同時(shí)調(diào)用 increment() 方法,仍然可能導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng),最終的 counter 值可能小于預(yù)期的值。

volatile 能替代鎖嗎?什么情況下可以?

不能完全替代鎖。volatile 只能保證可見性和禁止指令重排序,但不能保證原子性。鎖(如 synchronized 和 ReentrantLock)可以保證原子性、可見性和有序性。

在以下情況下,volatile 可以替代鎖:

  • 狀態(tài)標(biāo)記: 當(dāng)一個(gè)變量的狀態(tài)只會(huì)被一個(gè)線程修改,而其他線程只是讀取該變量的狀態(tài)時(shí),可以使用 volatile。例如,一個(gè)線程負(fù)責(zé)啟動(dòng)和停止服務(wù),其他線程只需要知道服務(wù)是否已經(jīng)啟動(dòng)。
public class Service {     private volatile boolean running = false;      public void start() {         running = true;         // ...啟動(dòng)服務(wù)的代碼     }      public void stop() {         running = false;         // ...停止服務(wù)的代碼     }      public boolean isRunning() {         return running;     } }
  • 單例模式(DCL 雙重檢查鎖定的改進(jìn)): 在單例模式中使用 volatile 可以防止指令重排序?qū)е碌膯栴}。
public class Singleton {     private volatile static Singleton instance;      private Singleton() {}      public static Singleton getInstance() {         if (instance == null) {             synchronized (Singleton.class) {                 if (instance == null) {                     instance = new Singleton();                 }             }         }         return instance;     } }

如何正確使用 volatile?有哪些常見的誤用場(chǎng)景?

正確使用 volatile 的關(guān)鍵在于理解其局限性。記住它只能保證可見性和禁止指令重排序,不能保證原子性。

正確使用場(chǎng)景:

  1. 一個(gè)線程寫,多個(gè)線程讀: 這是 volatile 最典型的應(yīng)用場(chǎng)景。例如,配置信息的更新,一個(gè)線程負(fù)責(zé)從配置文件中讀取最新的配置信息,并更新到 volatile 變量中,其他線程可以讀取最新的配置信息。

  2. 作為觸發(fā)器: 一個(gè)線程通過修改 volatile 變量的值來通知其他線程執(zhí)行某些操作。

常見的誤用場(chǎng)景:

  1. 復(fù)合操作: 不要將 volatile 用于復(fù)合操作,例如 i++,i–。

  2. 多個(gè)線程寫: 如果多個(gè)線程同時(shí)修改 volatile 變量的值,仍然會(huì)存在數(shù)據(jù)競(jìng)爭(zhēng)問題。

  3. 依賴當(dāng)前值的操作: 類似 if (status == READY) { doSomething(); },volatile 只能保證讀取到最新的 status 值,但不能保證在 doSomething() 執(zhí)行之前 status 沒有被其他線程修改。

volatile 與 synchronized 的區(qū)別是什么?

volatile 和 synchronized 是 Java 中用于解決并發(fā)問題的兩個(gè)重要關(guān)鍵字,它們之間有明顯的區(qū)別:

  1. 功能: volatile 只能保證可見性和禁止指令重排序,不能保證原子性。synchronized 可以保證原子性、可見性和有序性。

  2. 鎖: volatile 不是鎖,它不會(huì)引起線程阻塞。synchronized 是一種互斥鎖,當(dāng)一個(gè)線程獲取到 synchronized 鎖時(shí),其他線程需要等待該線程釋放鎖才能執(zhí)行。

  3. 開銷: volatile 的開銷比 synchronized 小。synchronized 會(huì)引起線程上下文切換,而 volatile 不會(huì)。

  4. 適用場(chǎng)景: volatile 適用于簡(jiǎn)單的狀態(tài)標(biāo)記,一個(gè)線程寫,多個(gè)線程讀的場(chǎng)景。synchronized 適用于需要保證原子性的場(chǎng)景,例如對(duì)共享變量進(jìn)行復(fù)合操作。

選擇使用 volatile 還是 synchronized,需要根據(jù)具體的并發(fā)場(chǎng)景進(jìn)行權(quán)衡。如果只需要保證可見性,且操作是原子的,則可以使用 volatile。如果需要保證原子性,或者有多個(gè)線程同時(shí)修改共享變量,則需要使用 synchronized。有時(shí)候,Lock 接口的實(shí)現(xiàn)類(例如 ReentrantLock)可能更適合,因?yàn)樗鼈兲峁┝烁`活的鎖機(jī)制。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊5 分享