Java中volatile關鍵字的作用 剖析Java volatile保證可見性的原理

volatile關鍵字在Java中主要用于保證線程環境下共享變量的可見性。1. 它通過禁止指令重排序,確保對volatile變量的寫操作發生在讀操作之前;2. 強制刷新緩存,使修改立即寫入主內存,并讓其他線程強制從主內存讀取最新值。但volatile不能保證原子性,例如i++這樣的復合操作仍需synchronized或atomicinteger來保證線程安全。與synchronized相比,volatile僅保證可見性,開銷較小,適用于單個變量的讀寫場景。正確使用volatile需要注意:僅用于共享變量、配合其他機制保證原子性、避免不必要的使用。其底層依賴內存屏障實現可見性和有序性,而在早期jvm中,聲明double和long為volatile也可保證寫操作的原子性。

Java中volatile關鍵字的作用 剖析Java volatile保證可見性的原理

Java中volatile關鍵字主要用于保證多線程環境下共享變量的可見性。簡單來說,當一個變量被聲明為volatile時,所有線程都會立即看到該變量的最新值,避免出現臟讀的情況。

Java中volatile關鍵字的作用 剖析Java volatile保證可見性的原理

剖析Java volatile保證可見性的原理

Java中volatile關鍵字的作用 剖析Java volatile保證可見性的原理

volatile關鍵字通過以下兩種方式保證可見性:

立即學習Java免費學習筆記(深入)”;

Java中volatile關鍵字的作用 剖析Java volatile保證可見性的原理

  1. 禁止指令重排序: volatile會阻止編譯器和處理器對指令進行重排序優化。這意味著,對volatile變量的寫操作一定會發生在讀操作之前,從而避免了因指令重排序導致的可見性問題。
  2. 強制刷新緩存: 當一個線程修改了volatile變量的值,會立即將該值刷新到主內存中。同時,其他線程在讀取該volatile變量時,會強制從主內存中重新讀取,而不是使用本地緩存。

為什么需要volatile關鍵字?

在多線程環境下,每個線程都有自己的工作內存,用于存儲共享變量的副本。當一個線程修改了共享變量的值,并不會立即同步到主內存中,而是先更新自己的工作內存。如果其他線程仍然使用本地緩存中的舊值,就會出現數據不一致的問題,這就是所謂的“可見性問題”。

volatile關鍵字就是為了解決這個問題而存在的。它確保了每個線程都能看到共享變量的最新值,從而保證了數據的一致性。

volatile一定能保證線程安全嗎?

不一定。volatile只能保證可見性,不能保證原子性。

原子性是指一個操作是不可中斷的,要么全部執行成功,要么全部不執行。例如,i++操作就不是原子性的,它實際上包含了三個步驟:

  1. 讀取i的值。
  2. 將i的值加1。
  3. 將結果寫回i。

如果在多線程環境下,多個線程同時執行i++操作,即使i被聲明為volatile,仍然可能出現線程安全問題。因為線程A在讀取i的值后,可能被線程B搶占CPU,線程B也讀取了i的值并進行了加1操作,然后線程A再繼續執行加1操作,最終導致i的值只增加了1,而不是2。

所以,如果需要保證原子性,還需要使用其他的同步機制,例如synchronized關鍵字或AtomicInteger類。

volatile與synchronized的區別

volatile和synchronized都可以用于解決多線程并發問題,但它們的作用和適用場景有所不同。

  • 作用: volatile主要用于保證可見性,synchronized既能保證可見性,又能保證原子性。
  • 開銷: volatile的開銷比synchronized小。synchronized會引起線程阻塞和上下文切換,而volatile只是簡單地禁止指令重排序和強制刷新緩存。
  • 適用場景: volatile適用于對單個變量的讀寫操作,且不需要保證原子性的場景。synchronized適用于需要保證原子性和可見性的復雜場景。

簡單來說,如果只需要保證變量的可見性,可以使用volatile。如果需要保證原子性,或者需要對多個變量進行同步操作,則需要使用synchronized。

如何正確使用volatile關鍵字?

正確使用volatile關鍵字需要注意以下幾點:

  1. 只用于修飾共享變量: volatile只能用于修飾多個線程共享的變量。如果變量只在單個線程中使用,則不需要聲明為volatile。
  2. 確保操作的原子性: volatile只能保證可見性,不能保證原子性。如果需要保證原子性,還需要使用其他的同步機制
  3. 避免過度使用: volatile雖然開銷較小,但仍然會帶來一定的性能損耗。因此,應該避免過度使用volatile,只在必要的時候才使用。

一個常見的volatile使用場景是作為狀態標志,例如:

volatile boolean running = true;  public void start() {     new Thread(() -> {         while (running) {             // 執行任務         }     }).start(); }  public void stop() {     running = false; }

在這個例子中,running變量被聲明為volatile,確保了當stop()方法被調用時,線程能夠立即看到running的值變為false,從而停止執行任務。

volatile底層實現原理是什么?

volatile的底層實現依賴于內存屏障(Memory Barrier)。內存屏障是一種CPU指令,用于強制刷新緩存,并禁止指令重排序。

當編譯器遇到volatile關鍵字時,會在生成字節碼時插入內存屏障指令。這些指令會告訴CPU:

  1. 在讀取volatile變量之前,必須從主內存中重新讀取。
  2. 在寫入volatile變量之后,必須立即將值刷新到主內存中。
  3. 禁止對volatile變量的讀寫操作進行指令重排序。

不同的CPU架構和JVM實現,內存屏障的具體實現方式可能有所不同。但其核心作用都是保證volatile變量的可見性和有序性。

為什么double和long類型的非原子操作,在某些情況下也需要volatile?

在早期的32位JVM中,對double和long類型的變量的寫操作可能不是原子性的。也就是說,一個64位的double或long變量的寫入操作可能被分成兩個32位的操作來執行。如果在多線程環境下,一個線程正在寫入double或long變量,而另一個線程正在讀取該變量,就可能讀到不完整的數據,導致數據不一致。

雖然現在的JVM基本都解決了這個問題,對double和long類型的變量的寫入操作默認是原子性的,但在某些特殊情況下,為了確保兼容性,仍然建議將double和long類型的共享變量聲明為volatile。

? 版權聲明
THE END
喜歡就支持一下吧
點贊8 分享