<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
前言:
單例模式的實現方法有很多種,如餓漢模式、懶漢模式、靜態內部類和列舉等,當面試官問到“為什麼單例模式一定要加 volatile?”時,那麼他指的是為什麼懶漢模式中的私有變數要加 volatile?
懶漢模式指的是物件的建立是懶載入的方式,並不是在程式啟動時就建立物件,而是第一次被真正使用時才建立物件。
要解釋為什麼要加 volatile?我們先來看懶漢模式的具體實現程式碼:
public class Singleton { // 1.防止外部直接 new 物件破壞單例模式 private Singleton() {} // 2.通過私有變數儲存單例物件【新增了 volatile 修飾】 private static volatile Singleton instance = null; // 3.提供公共獲取單例物件的方法 public static Singleton getInstance() { if (instance == null) { // 第 1 次效驗 synchronized (Singleton.class) { if (instance == null) { // 第 2 次效驗 instance = new Singleton(); } } } return instance; } }
從上述程式碼可以看出,為了保證執行緒安全和高效能,程式碼中使用了兩次 if 和 synchronized 來保證程式的執行。那既然已經有 synchronized 來保證執行緒安全了,為什麼還要給變數加 volatile 呢? 在解釋這個問題之前,我們先要搞懂一個前置知識:volatile 有什麼用呢?
volatile 有兩個主要的作用,第一,解決記憶體可見性問題,第二,防止指令重排序。
所謂記憶體可見性問題,指的是多個執行緒同時操作一個變數,其中某個執行緒修改了變數的值之後,其他執行緒感知不到變數的修改,這就是記憶體可見性問題。 而使用 volatile 就可以解決記憶體可見性問題,比如以下程式碼,當沒有新增 volatile 時,
它的實現如下:
private static boolean flag = false; public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { // 如果 flag 變數為 true 就終止執行 while (!flag) { } System.out.println("終止執行"); } }); t1.start(); // 1s 之後將 flag 變數的值修改為 true Thread t2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("設定 flag 變數的值為 true!"); flag = true; } }); t2.start(); }
以上程式的執行結果如下:
然而,以上程式執行了 N 久之後,依然沒有結束執行,這說明執行緒 2 在修改了 flag 變數之後,執行緒 1 根本沒有感知到變數的修改。
那麼接下來,我們嘗試給 flag 加上 volatile,實現程式碼如下:
public class volatileTest { private static volatile boolean flag = false; public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { // 如果 flag 變數為 true 就終止執行 while (!flag) { } System.out.println("終止執行"); } }); t1.start(); // 1s 之後將 flag 變數的值修改為 true Thread t2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("設定 flag 變數的值為 true!"); flag = true; } }); t2.start(); } }
以上程式的執行結果如下:
從上述執行結果我們可以看出,使用 volatile 之後就可以解決程式中的記憶體可見性問題了。
指令重排序是指在程式執行過程中,編譯器或 JVM 常常會對指令進行重新排序,已提高程式的執行效能。 指令重排序的設計初衷確實很好,在單執行緒中也能發揮很棒的作用,然而在多執行緒中,使用指令重排序就可能會導致執行緒安全問題了。
所謂執行緒安全問題是指程式的執行結果,和我們的預期不相符。比如我們預期的正確結果是 0,但程式的執行結果卻是 1,那麼這就是執行緒安全問題。
而使用 volatile 可以禁止指令重排序,從而保證程式在多執行緒執行時能夠正確執行。
回到主題,我們在單例模式中使用 volatile,主要是使用 volatile 可以禁止指令重排序,從而保證程式的正常執行。這裡可能會有讀者提出疑問,不是已經使用了 synchronized 來保證執行緒安全嗎?那為什麼還要再加 volatile 呢?
看下面的程式碼:
public class Singleton { private Singleton() {} // 使用 volatile 禁止指令重排序 private static volatile Singleton instance = null; public static Singleton getInstance() { if (instance == null) { // ① synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // ② } } } return instance; } }
注意觀察上述程式碼,我標記了第 ① 處和第 ② 處的兩行程式碼。給私有變數加 volatile 主要是為了防止第 ② 處執行時,也就是“instance = new Singleton()”執行時的指令重排序的,這行程式碼看似只是一個建立物件的過程,然而它的實際執行卻分為以下 3 步:
試想一下,如果不加 volatile,那麼執行緒 1 在執行到上述程式碼的第 ② 處時就可能會執行指令重排序,將原本是 1、2、3 的執行順序,重排為 1、3、2。但是特殊情況下,執行緒 1 在執行完第 3 步之後,如果來了執行緒 2 執行到上述程式碼的第 ① 處,判斷 instance 物件已經不為 null,但此時執行緒 1 還未將物件範例化完,那麼執行緒 2 將會得到一個被範例化“一半”的物件,從而導致程式執行出錯,這就是為什麼要給私有變數新增 volatile 的原因了。
使用 volatile 可以解決記憶體可見性問題和防止指令重排序,我們在單例模式中使用 volatile 主要是使用 volatile 的後一個特性(防止指令重排序),從而避免多執行緒執行的情況下,因為指令重排序而導致某些執行緒得到一個未被完全範例化的物件,從而導致程式執行出錯的情況。
到此這篇關於為什麼Java單例一定要加 volatile的文章就介紹到這了,更多相關Java單例內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45