请谈谈你对volatile的理解?--最近小李子与面试官的一场“硬核较量”
目錄
1. 面試官:“請談談你對volatile的理解”
2. 面試官接著問:“既然volatile不能保證原子性,那工作中如何解決這個問題呢?”
3. 官試官:“volatile是如何保證多線程環境下的可見性,JAVA內存模型JMM談談你對它的了解”
4. 官試官:“你能編寫一個volatile修飾的變量在多線程環境可見性示例不?”
5.?面試官:“為什么volatile不保證原子性呀,你知道底層原理么?”
6. 面試官:“你可以編寫一下,volatile不保證原子性的解決示例?”
7. 面試官:volatile能禁止指令重排 ,為什么有指令重排,volatile中怎樣做到禁止指令重排?
8. 面試官:你在哪些地方用到過volatile?手寫一個Volatile單例
?
1. 面試官:“請談談你對volatile的理解”
小李子竊竊私語,這不是正好前段時間復習且項目中使用到的volatile關鍵字么?于是小李子自信地答道:
volatile是Java虛擬機提供的輕量級的同步機制,volatile 是一個類型修飾符。volatile 的作用是作為指令關鍵字,確保本條指令不會因編譯器的優化而進行指令重排序,同時保證多線程環境下變量值變修改后,其他線程可見性;但volatile不保證原子性哦。使用示例:
volatile int number = 0; // 使用volatile 修飾整型變量number,以保證多線程環境下可見性及禁止指令重排。注:volatile不保證原子性- 1.1 保證多線程的可見性
- 1.2 不保證原子性
- 1.3 禁止指令重排,一般在單例模式下使用較多
2. 面試官接著問:“既然volatile不能保證原子性,那工作中如何解決這個問題呢?”
小李子心想,還來了個連環炮,好在我有準備。于是比利比利地回答:
工作中,我們有兩種方式規避這個問題:
1.?使用JDK提供的?Atomic原子類
比如:AtomicInteger來聲明變量,是采用了CAS 比較并交換 compareAndSwapInt,底層調用的是native方法,其意思是通過hotspot底層c/c++方法實現。最終實現是調用了?cmpxchg,cmpxchg指令在多線程下也是有可能被打斷,所以在加入lock指令?不允許其他線程訪問這塊內存區域的數據。
2. 加鎖,如synchronized 或?ReentrantLock
3. 官試官:“volatile是如何保證多線程環境下的可見性,JAVA內存模型JMM談談你對它的了解”
小李子心底陣陣發涼,前面已經回答的很好,還接著問,有完沒完!沒辦法,回憶一下,繼續答道:
Java內存模型(Java Memory Model,簡稱JMM) ?本身是一種抽象的概念并不真實存在,它描述的是一組規則或規范通過規范定制了程序中各個變量(包括實例字段、靜態字段和構成數組對象的元素)的訪問方式。
JMM關于同步規定:
由于JVM運行程序的實體是線程,而每個線程創建時JVM都會為其創建一個工作內存(有些地方成為棧空間),工作內存是每個線程的私有數據區域,而Java內存模型中規定所有變量都存儲在主內存,主內存是共享內存區域,所有線程都可訪問,但線程對變量的操作 (讀取賦值等) 必須在工作內存中進行,首先要將變量從主內存拷貝到自己的工作空間,然后對變量進行操作,操作完成再將變量寫回主內存,不能直接操作主內存中的變量,各個線程中的工作內存儲存著主內存中的變量副本拷貝,因此不同的線程無法訪問對方的工作內存,線程間的通訊(傳值) 必須通過主內存來完成。?
這樣子吧,我畫一個JMM內存模型圖出來看看就清楚了。JMM內存模型圖如下:
4. 官試官:“你能編寫一個volatile修飾的變量在多線程環境可見性示例不?”
小李子聽到這個就想要罵娘了,還得編一個示例。不過,小Case,小李子接過鍵盤,啪啦啪啦地敲下代碼:
package com.java.meet.c01_11_volatile;import java.time.LocalDateTime; import java.util.concurrent.TimeUnit;class MyResource {//int number = 0; // 此行代碼在多線程環境下,變量修改后不可見volatile int number = 0; // 此行代碼在多線程環境下,變量修改后可見,因為變量被 volatile修飾public void addT060(){this.number = 60;}}public class C_02_Volatile_SeeOK {public static void main(String[] args) { // main是一切方法的運行入口seeOkByVolatile();}// volatile 可以保證可見性,及時通知其它線程,主物理內存的值已經被修改。// volatile 是通過內存屏障實現private static void seeOkByVolatile() {MyResource res = new MyResource(); // 資源類new Thread(() -> {System.out.println(LocalDateTime.now() + "\t" + Thread.currentThread().getName() + "線程\t當前值為:" + res.number);// 暫停一會兒線程try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e){ e.printStackTrace();}res.addT060();System.out.println(LocalDateTime.now() + "\t" + Thread.currentThread().getName() + "線程\t當前值為:" + res.number);}, "AAAA").start();// 第2個線程就是我們的main線程while(res.number == 0){// main線程就一直在這里等待循環,走到number值不再等于零}System.out.println(LocalDateTime.now() + "\t" + Thread.currentThread().getName() + "線程\t當前值為" + res.number);}}沒有被volatile修飾變量,在多線程中被修改值,測試結果如下:?
被volatile修飾變量,在多線程中被修改值,測試結果如下:?
敲出來代碼了,哈哈,小李子心里哈哈的暗自高興~?
5.?面試官:“為什么volatile不保證原子性呀,你知道底層原理么?”
小李子,捋了下思路,這個So easy嘛。說道:number++在多線程下是非線程安全的。當我們添加volatile 關鍵字修飾時,n++ 同樣被拆分成了3個指令,在多線程環境下,依然不能保證原子性。如下圖,是volatile 修飾整型變量n,對n++方法進行反匯編。
package com.java.meet.c01_11_volatile;/*** 6_volatile不保證原子理論解釋** 使用javap -c或javap -verbose*/public class C_06_T1 {volatile int n = 0;/*** MyData.java ====> MyData.class ===> 字節碼* n++ 被拆分成了3個指令* 執行getfield拿到原始n;* 執行iadd進行加1操作;* 執行putfield寫把累加后的值寫回** 查看字節碼,需要配置好External tools*//**/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/bin/javap -c com.java.meet.c01_11_volatile.C_06_T1Compiled from "C_06_T1.java"public class com.java.meet.c01_11_volatile.C_06_T1 {volatile int n;public com.java.meet.c01_11_volatile.C_06_T1();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: iconst_06: putfield #2 // Field n:I9: returnpublic void add();Code:0: aload_01: dup2: getfield #2 // Field n:I5: iconst_16: iadd7: putfield #2 // Field n:I10: return}Process finished with exit code 0*/public void add(){n++;} }6. 面試官:“你可以編寫一下,volatile不保證原子性的解決示例?”
小李子胸有成竹的說:這樣吧,我演示一個volatile修飾的變量在多線程環境下不保證原子性的及其中一個解決volatile不保證原子性辦法。其中,可以通過JDK提供的Atomic原子類來保證原子操作,于是,拿起鍵盤噠噠地敲起來:
package com.java.meet.c01_11_volatile;import java.util.concurrent.atomic.AtomicInteger;class MyNumber {volatile int number = 0;// 請注意,此時number前面是加了volatile關鍵字修飾的,volatile不保證原子性public void addPlusPlus(){number++;}// 使用JDK提供的原子類,可以解決volatile不保證原子性問題,采用CAS比較并交換AtomicInteger atomicInteger = new AtomicInteger();public void addMyAtomic(){atomicInteger.getAndIncrement();} }public class C_03_Volatile_NotSupportAtomic {public static void main(String[] args) { // main是一切方法的運行入口notSupportAtomicByVolatile();}// volatile 不保證原子性 及 使用JDK Atomic原子類解決volatile不保證原子性問題private static void notSupportAtomicByVolatile() {MyNumber res = new MyNumber();for (int i = 0; i < 20; i++) {new Thread(() -> {for (int j = 0; j < 1000; j++) {res.addPlusPlus();res.addMyAtomic();}}, String.valueOf(i)).start();}// 需要等待上面20個線程都全部計算完成后,再用main線程取得最終的結果值看是多少?while (Thread.activeCount() > 2){Thread.yield(); // yield我不執行,讓其他的線程更好地執行}System.out.println(Thread.currentThread().getName() + "\t int type,finally number value:" + res.number);System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type,finally number value:" + res.atomicInteger);} }volatile不保證原子性測試結果如下:?
7. 面試官:volatile能禁止指令重排 ,為什么有指令重排,volatile中怎樣做到禁止指令重排?
小李子,心里苦呀,我只是想拿月薪30K的薪資,還得挖那么深,沒有辦法。繼續回答:
計算機在執行程序時,為了提高性能,編譯器和處理器常常會做指令重排,一把分為以下3種
單線程環境里面確保程序是最終執行結果和代碼順序執行的結果一致。處理器在進行重新排序是必須要考慮指令之間的數據依賴性。
多線程環境中線程交替執行,由于編譯器優化重排的存在,兩個線程使用的變量能否保持一致性是無法確定的,結果無法預測。
volatile實現禁止指令重排優化,從而避免多線程環境下程序出現亂序執行的現象。
先了解一個概念,內存屏障(Memory Barrier)又稱內存柵欄,是一個CPU指令,它的作用有兩個:
由于編譯器和處理器都能執行指令重排優化。如果在指令插入一條Memory B?和這條Memory Barrier指令重排序,也就是說通過插入內存屏障禁止在內存屏障前后的指令執行重排序優化。內存屏障另外一個作用是強制刷出各種CPU的緩存數據,因此任何CPU上的線程都能讀取到這些數據,因此任何CPU上的線程都能讀取到這些數據的最新版本。
- 對volatile變量進行寫操作時,會在寫操作后加入一條store屏障指令,將工作內存中的共享變量值刷新回到主內存
- 對volatile變量進行讀操作時,會在讀操作前加入一條load屏障指令,從主內存中讀取共享變量
8. 面試官:你在哪些地方用到過volatile?手寫一個Volatile單例
小李子喜出望外,這不是送分題。通過DCL雙端檢查 + Volatile,來實現,卡拉卡拉地寫下如下代碼:
package com.java.meet.c01_11_volatile;import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.concurrent.TimeUnit;/*** 單例模式:volatile版本**/ public class C_10_SingletonDemo {/*** 多線程環境下,單例模式下,需要DCL機制 + volatile禁止指令重排*///private static C_10_SingletonDemo instance = null; // 在非常高的并發情況下,可能獲取的對象不是同一個private static volatile C_10_SingletonDemo instance = null; // 在非常高的并發情況下,DCL雙端檢查 + volatile 可以保證獲取的是同一個對象// 單例類構造方法私有化private C_10_SingletonDemo() {System.out.println(Thread.currentThread().getName() + "\t我是構造方法SingletonDemo()");}// 如果單純依靠 DCL (Double Check Lock雙端檢鎖機制),還是有問題,可能運行1000萬次,才出一次問題/*** DCL(雙端檢鎖) 機制不一定線程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排** 原因在于某一個線程在執行到第一次檢測,讀取到的instance不為null時,instance的引用對象可能沒有完成初始化.** instance=new SingletonDem(); 可以分為以下步驟(偽代碼)** memory=allocate(); //1.分配對象內存空間** instance(memory); //2.初始化對象** instance=memory; //3.設置instance的指向剛分配的內存地址,此時instance!=null** 步驟2和步驟3不存在數據依賴關系.而且無論重排前還是重排后程序執行的結果在單線程中并沒有改變,因此這種重排優化是允許的.** memory=allocate(); //1.分配對象內存空間** instance=memory; //3.設置instance的指向剛分配的內存地址,此時instance!=null 但對象還沒有初始化完.*/public static C_10_SingletonDemo getInstance(){if (instance == null){synchronized (C_10_SingletonDemo.class){if (instance == null){instance = new C_10_SingletonDemo();}}}if (instance == null) {System.out.println("----");}return instance;}// List本身是不安全的, 使用以下才安全public static List<C_10_SingletonDemo> list = Collections.synchronizedList(new ArrayList<C_10_SingletonDemo>());public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 30; i++) {new Thread(() -> {list.add(C_10_SingletonDemo.getInstance());}, String.valueOf(i)).start();}// 等待上述操作完成TimeUnit.SECONDS.sleep(2);boolean oneSingleObject = true;for (int i = 0; i < list.size(); i++) {for (int j = i+1; j < list.size(); j++) {if (list.get(i) != list.get(j)) {System.out.println("此單例,創建了不同的對象實例!!!\t" + i + " "+ list.get(i) + "\t" + list.get(j));oneSingleObject = false;}}}if (oneSingleObject) {System.out.println("通過單例類獲取:" + list.size() + " 次依然是同一個對象!");}} }DCL雙端檢查 + Volatile 單例測試結果如下:?
面試官,看來你對volatile掌握得還不錯哈~?
今天面試,先到這里,回去等通過吧。
文章最后,給大家推薦一些受歡迎的技術博客鏈接:
歡迎掃描下方的二維碼或 搜索 公眾號“大數據高級架構師”,我們會有更多、且及時的資料推送給您,歡迎多多交流!
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
?? ? ??
?
總結
以上是生活随笔為你收集整理的请谈谈你对volatile的理解?--最近小李子与面试官的一场“硬核较量”的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自己搭建服务器要多少钱?
- 下一篇: 计算机原理(3)主板上的CPU,存储器,