撕開volatile的外衣一飽眼福
一、概述
由於疫情的影響,今天才回到長沙,估計各位道友跟我一樣早就想發洩下內心的躁動,今天就滿足下各位的要求,撕開 volatile的外衣讓大家一飽眼福。今天打算從下面三個方面來解剖
volatile 關鍵字是什什麼?
volatile 關鍵字能解決什麼問題?
使用場景是什麼 ?
volatile關鍵字實現的原理理?
二、 volatile 關鍵字是什什麼?
在 Sun 的 JDK 官⽅方⽂文件是這樣形容 volatile 的:
This means that changes to a volatile variable are always visible to other threads. What's more, it also means that when a thread reads a volatile variable, it sees not just the latest change to the volatile, but also the side effects of the code that led up the change.
翻譯過來:如果一個變數加了 volatile 關鍵字,就會告訴編譯器和 JVM 的記憶體模型:這 個變數是對所有執行緒共享的、可⻅的,每次 JVM 都會讀取最新寫⼊的值並使其最新值在 所有 CPU 可見。volatile 可以保證執行緒的可見性並且提供了一定的有序性,但是無法保 證原⼦子性。在 JVM 底層 volatile 是採⽤記憶體屏障來實現的。
透過這段話,我們可以知道 volatile 有兩個特性:
保證可見性、不保證原⼦性
禁⽌指令重排序
三、可見性、原子性、有序性、重排序為何物
1、先說三性
可見性、有序性、原子性統稱為併發三大核心問題。如果把併發程式設計的祖墳挖出來、你會發現一切的根源至:
1、硬體的發展中,一直存在⼀個⽭盾,CPU、記憶體、I/O裝置的速度差異。
2、速度排序:CPU >> 記憶體 >> I/O裝置
3、為了了平衡這三者的速度差異,做了了如下最佳化:
1) CPU 增加了了快取,以均衡記憶體與CPU的速度差異
2)作業系統增加了了程式、執行緒,以分時復⽤用CPU,進⽽而均衡I/O裝置與CPU的速度差異;
3) 編譯程式最佳化指令執⾏行行次序,使得快取能夠得到更更加合理理地利利⽤用。
我經常說的一句話、看問題一定看到問題的本質,根源,你才能將這個設計或者概念深入骨髓。才能從靈魂上駕馭它!玩弄她 …
2、再說重排序
定義:在執行程式時為了提高效能,編譯器和處理器常常會對執行做重排序。及程式碼的執行順序重新排序執行。主要包括三個層面的重排序:
編譯器重排序
指令級重排序
處理器重排序
說得俗氣點就是為了提高程式碼執行的效率而做的最佳化,最佳化,最佳化 ,重要的事情說三遍。
四、原子性和可見性
原⼦性是指⼀個操作或多個操作要麼全部執行並且執行的過程不會被任何因素打斷,要麼 都不執行。性質和資料庫中事務⼀樣,⼀組操作要麼都成功,要麼都失敗。看下⾯⼏個簡單例⼦來理解原子性 :
1 i=0; //1
2 j=i; //2
3 i++; //3
4 i=j+1; //4
在看答案之前,可以先思考⼀一下上⾯面四個操作,哪些是原⼦子操作 ?哪些是⾮非原⼦子操作? 答案揭曉:
1——是:在Java中,對基本資料型別的變數賦值操作都是原⼦性操作(Java 有八⼤基本資料型別)
2——不是:包含兩個動作:讀取 i 值,將 i 值賦值給 j
3——不是:包含了三個動作:讀取 i 值,i+1,將 i+1 結果賦值給 i
4——不是:包含了三個動作:讀取 j 值,j+1,將 j+1 結果賦值給 i
也就是說,只有簡單的讀取、賦值 (⽽且必須是將數字賦值給某個變量,變量之間的相互 賦值不是原⼦操作)才是原⼦操作。
注 :由於以前的作業系統是 32 位, 64 位資料(long 型,double 型)在 Java 中是 8 個字 節表示,一共佔⽤用64 位,因此需要分成兩次操作採⽤完成一個變數的賦值或者讀取操作。隨著 64 位作業系統越來越普及,在 64 位的 HotSpot JVM 實現中,對64 位資料 (long 型,double 型)做原⼦性處理(由於 JVM 規範沒有明確規定,不排除別的 JVM 實現還是按照 32 位的方式處理)。
在單執行緒環境中我們可以認為上述步驟都是原子性操作,但是在多執行緒環境下, Java 只保證了了上述基本資料型別的賦值操作是原⼦性的,其他操作都有可能在運算過程中出現錯誤。為此在多執行緒環境下為了保證⼀些操作的原⼦性引⼊了鎖和 synchronized 等關鍵字。
上⾯說到 volatile 關鍵字保證了變數的可⻅性,不保證原⼦性。原⼦性已經說了,下⾯說 下可見性。
可見性其實和 Java 記憶體模型的設定有關:Java 記憶體模型規定所有的變量都是存在主存 (執行緒共享區域)當中,每個執行緒都有自⼰的⼯工作記憶體(私有記憶體)。執行緒對變數的所有操作都必須在工作記憶體中進⾏,⽽不直接對主存進行操作。並且每個執行緒不能訪問其他線 程的⼯作記憶體。
舉個簡單栗⼦ :
1 ⽐如上面 i++ 操作,在 Java 中,執行 i++ 語句:
2 執行執行緒⾸先從主存中讀取 i(原始值)到⼯作記憶體中,然後在⼯作記憶體中執⾏運算 +1
3
4 操作(主存的 i 值未變),最後將運算結果重新整理到主存中。
資料運算是在執行執行緒的私有記憶體中進行的,執行緒執⾏完運算後,並不一定會⽴即將運算結果重新整理到主存中 (雖然最後一定會更新主存),重新整理到主存動作是由 CPU ⾃⾏選擇⼀一個合適的時間觸發的。假設數值未更新到主存之前,當其他執行緒去讀取時(而且優先讀取的是工作記憶體中的資料⽽⾮主存),此時主存中可能還是原來的舊值,就有可能導致運算結果出錯。
以下程式碼是 測試 程式碼 :
1 public class VolatileDemo4 {
2 private boolean flag = false;
3 class ThreadOne implements Runnable {
4 @Override
5 public void run() {
6 while (!flag) {
7 System.out.println("執⾏行行操作");
8 try {
9 Thread.sleep(1000L);
10 } catch (InterruptedException e) {
11 e.printStackTrace();
12 }
13 }
14 System.out.println("任務停⽌止");
15 }
16 }
17 class ThreadTwo implements Runnable {
18 @Override
19 public void run() {
20 try {
21 Thread.sleep(2000L);
22 System.out.println("flag 狀態改變");
23 flag = true;
24 } catch (InterruptedException e) {
25 e.printStackTrace();
26 }
27 }
28 }
29 public static void main(String[] args) {
30 VolatileDemo4 testVolatile = new VolatileDemo4();
31 Thread thread1 = new Thread(testVolatile.new ThreadOne());
32 Thread thread2 = new Thread(testVolatile.new ThreadTwo());
33 thread1.start();
34 thread2.start();
35 }
36 }
上述結果有可能線上程 2 執行完 flag = true 之後,並不能保證執行緒 1 中的 while 能立即停止迴圈,原因在於 flag 狀態首先是線上程 2 的私有記憶體中改變的,重新整理到主存的時機不固定,⽽且執行緒 1 讀取 flag 的值也是在自⼰的私有記憶體中,而執行緒 1 的私有記憶體中 flag 仍為 false,這樣就有可能導致執行緒仍然會繼續 while 迴圈。運行結果如下:
執行操作
執行操作
執行操作
flag 狀態改變
任務停⽌止
避免上述不可預知問題的發⽣就是⽤ volatile 關鍵字修飾 flag,volatile 修飾的共享變量 可以保證修改的值會在操作後立即更新到主存里面,當有其他執行緒需要操作該變量時,不是從私有記憶體中讀取,⽽是強制從主存中讀取新值。即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是⽴即可見的。
由於篇幅原因,未完待續 …
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69940641/viewspace-2913827/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 為我開發的API新增華麗的外衣API
- 元宇宙只是未來技術演化的外衣元宇宙
- 手撕Flutter開發Flutter
- Java 開發, volatile 你必須瞭解一下Java
- volatile的理解
- volatile的用法
- Volatile可見性分析(一)
- 手撕一個spirng IoC的過程
- Volatile的那些事
- 成都開手撕發具票
- 天津開手撕發具票
- 原本只是單純的技術 卻被披上夢想的外衣
- volatile
- Java Volatile的一個實際應用場合Java
- 成都開手撕發票“哪裡有開”
- 關於太原開手撕發票
- Volatile的底層原理
- 你說一下對Java中的volatile的理解吧Java
- 北上廣等一線城市IT崗位已接近飽和?
- 天津開手撕發票‘如何開哪裡有開’
- 提前體驗飽受期待的Windows TerminalWindows
- 深圳開手撕發具票#哪裡可以開#
- 深圳開手撕發具票“哪裡可以開”
- 長春開手撕發票"哪裡能開"
- 關於福州開手撕發票-福州開票
- 關於北京開手撕發票-北京開票
- 關於南京開手撕發票-南京開票
- 小米吃到飽流量卡發售,小米吃到飽無限量卡在哪申請?
- Volatile關鍵字&&DCL單例模式,volatile 和 synchronized 的區別單例模式synchronized
- js陣列方法(管飽)JS陣列
- 貴陽開手撕發具票"怎麼開如何開"
- 成都開手撕發票“怎麼開哪裡有開”
- 天津開手撕發票‘怎麼開哪裡有開’
- 上海開手撕發票“怎麼開哪裡有開”
- 一文吃透Volatile,征服面試官面試
- 每日一問:談談 volatile 關鍵字
- volatile與synchronized的區別synchronized
- volatile與Atomic的比較