最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

為什么Java單例模式一定要加?volatile

 更新時(shí)間:2022年05月27日 16:31:48   作者:??Java中文社群????  
這篇文章主要介紹了為什么Java單例一定要加volatile,指的是為什么懶漢模式中的私有變量要加volatile?帶著疑問(wèn)一起學(xué)習(xí)下面文章內(nèi)容吧

前言:

單例模式的實(shí)現(xiàn)方法有很多種,如餓漢模式、懶漢模式、靜態(tài)內(nèi)部類(lèi)和枚舉等,當(dāng)面試官問(wèn)到“為什么單例模式一定要加 volatile?”時(shí),那么他指的是為什么懶漢模式中的私有變量要加 volatile?

懶漢模式指的是對(duì)象的創(chuàng)建是懶加載的方式,并不是在程序啟動(dòng)時(shí)就創(chuàng)建對(duì)象,而是第一次被真正使用時(shí)才創(chuàng)建對(duì)象。

要解釋為什么要加 volatile?我們先來(lái)看懶漢模式的具體實(shí)現(xiàn)代碼:

public class Singleton {
    // 1.防止外部直接 new 對(duì)象破壞單例模式
    private Singleton() {}
    // 2.通過(guò)私有變量保存單例對(duì)象【添加了 volatile 修飾】
    private static volatile Singleton instance = null;
    // 3.提供公共獲取單例對(duì)象的方法
    public static Singleton getInstance() {
        if (instance == null) { // 第 1 次效驗(yàn)
            synchronized (Singleton.class) {
                if (instance == null) { // 第 2 次效驗(yàn)
                    instance = new Singleton(); 
                }
            }
        }
        return instance;
    }
}

從上述代碼可以看出,為了保證線程安全和高性能,代碼中使用了兩次 if 和 synchronized 來(lái)保證程序的執(zhí)行。那既然已經(jīng)有 synchronized 來(lái)保證線程安全了,為什么還要給變量加 volatile 呢? 在解釋這個(gè)問(wèn)題之前,我們先要搞懂一個(gè)前置知識(shí):volatile 有什么用呢?

1.volatile 作用

volatile 有兩個(gè)主要的作用,第一,解決內(nèi)存可見(jiàn)性問(wèn)題,第二,防止指令重排序。

1.1 內(nèi)存可見(jiàn)性問(wèn)題

所謂內(nèi)存可見(jiàn)性問(wèn)題,指的是多個(gè)線程同時(shí)操作一個(gè)變量,其中某個(gè)線程修改了變量的值之后,其他線程感知不到變量的修改,這就是內(nèi)存可見(jiàn)性問(wèn)題。 而使用 volatile 就可以解決內(nèi)存可見(jiàn)性問(wèn)題,比如以下代碼,當(dāng)沒(méi)有添加 volatile 時(shí),

它的實(shí)現(xiàn)如下:

private static boolean flag = false;
public static void main(String[] args) {
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            // 如果 flag 變量為 true 就終止執(zhí)行
            while (!flag) {

            }
            System.out.println("終止執(zhí)行");
        }
    });
    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("設(shè)置 flag 變量的值為 true!");
            flag = true;
        }
    });
    t2.start();
}

以上程序的執(zhí)行結(jié)果如下: 

 然而,以上程序執(zhí)行了 N 久之后,依然沒(méi)有結(jié)束執(zhí)行,這說(shuō)明線程 2 在修改了 flag 變量之后,線程 1 根本沒(méi)有感知到變量的修改。

那么接下來(lái),我們嘗試給 flag 加上 volatile,實(shí)現(xiàn)代碼如下:

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 就終止執(zhí)行
                while (!flag) {

                }
                System.out.println("終止執(zhí)行");
            }
        });
        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("設(shè)置 flag 變量的值為 true!");
                flag = true;
            }
        });
        t2.start();
    }
}

以上程序的執(zhí)行結(jié)果如下: 

 從上述執(zhí)行結(jié)果我們可以看出,使用 volatile 之后就可以解決程序中的內(nèi)存可見(jiàn)性問(wèn)題了。

1.2 防止指令重排序

指令重排序是指在程序執(zhí)行過(guò)程中,編譯器或 JVM 常常會(huì)對(duì)指令進(jìn)行重新排序,已提高程序的執(zhí)行性能。 指令重排序的設(shè)計(jì)初衷確實(shí)很好,在單線程中也能發(fā)揮很棒的作用,然而在多線程中,使用指令重排序就可能會(huì)導(dǎo)致線程安全問(wèn)題了。

所謂線程安全問(wèn)題是指程序的執(zhí)行結(jié)果,和我們的預(yù)期不相符。比如我們預(yù)期的正確結(jié)果是 0,但程序的執(zhí)行結(jié)果卻是 1,那么這就是線程安全問(wèn)題。

而使用 volatile 可以禁止指令重排序,從而保證程序在多線程運(yùn)行時(shí)能夠正確執(zhí)行。

2.為什么要用 volatile?

回到主題,我們在單例模式中使用 volatile,主要是使用 volatile 可以禁止指令重排序,從而保證程序的正常運(yùn)行。這里可能會(huì)有讀者提出疑問(wèn),不是已經(jīng)使用了 synchronized 來(lái)保證線程安全嗎?那為什么還要再加 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;
    }
}

注意觀察上述代碼,我標(biāo)記了第 ① 處和第 ② 處的兩行代碼。給私有變量加 volatile 主要是為了防止第 ② 處執(zhí)行時(shí),也就是“instance = new Singleton()”執(zhí)行時(shí)的指令重排序的,這行代碼看似只是一個(gè)創(chuàng)建對(duì)象的過(guò)程,然而它的實(shí)際執(zhí)行卻分為以下 3 步:

  • 創(chuàng)建內(nèi)存空間。
  • 在內(nèi)存空間中初始化對(duì)象 Singleton。
  • 將內(nèi)存地址賦值給 instance 對(duì)象(執(zhí)行了此步驟,instance 就不等于 null 了)。

試想一下,如果不加 volatile,那么線程 1 在執(zhí)行到上述代碼的第 ② 處時(shí)就可能會(huì)執(zhí)行指令重排序,將原本是 1、2、3 的執(zhí)行順序,重排為 1、3、2。但是特殊情況下,線程 1 在執(zhí)行完第 3 步之后,如果來(lái)了線程 2 執(zhí)行到上述代碼的第 ① 處,判斷 instance 對(duì)象已經(jīng)不為 null,但此時(shí)線程 1 還未將對(duì)象實(shí)例化完,那么線程 2 將會(huì)得到一個(gè)被實(shí)例化“一半”的對(duì)象,從而導(dǎo)致程序執(zhí)行出錯(cuò),這就是為什么要給私有變量添加 volatile 的原因了。

總結(jié)

使用 volatile 可以解決內(nèi)存可見(jiàn)性問(wèn)題和防止指令重排序,我們?cè)趩卫J街惺褂?volatile 主要是使用 volatile 的后一個(gè)特性(防止指令重排序),從而避免多線程執(zhí)行的情況下,因?yàn)橹噶钪嘏判蚨鴮?dǎo)致某些線程得到一個(gè)未被完全實(shí)例化的對(duì)象,從而導(dǎo)致程序執(zhí)行出錯(cuò)的情況。

到此這篇關(guān)于為什么Java單例一定要加 volatile的文章就介紹到這了,更多相關(guān)Java單例內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java時(shí)間類(lèi)庫(kù)Timer的使用方法與實(shí)例詳解

    Java時(shí)間類(lèi)庫(kù)Timer的使用方法與實(shí)例詳解

    這篇文章主要介紹了Jave時(shí)間類(lèi)庫(kù)Timer的使用方法與實(shí)例詳解,需要的朋友可以參考下
    2020-02-02
  • java常用工具類(lèi) Random隨機(jī)數(shù)、MD5加密工具類(lèi)

    java常用工具類(lèi) Random隨機(jī)數(shù)、MD5加密工具類(lèi)

    這篇文章主要為大家詳細(xì)介紹了Java常用工具類(lèi),Random隨機(jī)數(shù)工具類(lèi)、MD5加密工具類(lèi),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • IDEA JarEditor編輯jar包方式(直接新增,修改,刪除jar包內(nèi)的class文件)

    IDEA JarEditor編輯jar包方式(直接新增,修改,刪除jar包內(nèi)的class文件)

    文章主要介紹了如何使用IDEA的JarEditor插件直接修改jar包內(nèi)的class文件,而不需要手動(dòng)解壓、反編譯和重新打包,通過(guò)該插件,可以更方便地進(jìn)行jar包的修改和測(cè)試
    2025-01-01
  • springboot配置多個(gè)數(shù)據(jù)源兩種方式實(shí)現(xiàn)

    springboot配置多個(gè)數(shù)據(jù)源兩種方式實(shí)現(xiàn)

    在我們的實(shí)際業(yè)務(wù)中可能會(huì)遇到;在一個(gè)項(xiàng)目里面讀取多個(gè)數(shù)據(jù)庫(kù)的數(shù)據(jù)來(lái)進(jìn)行展示,spring對(duì)同時(shí)配置多個(gè)數(shù)據(jù)源是支持的,本文主要介紹了springboot配置多個(gè)數(shù)據(jù)源兩種方式實(shí)現(xiàn),感興趣的可以了解一下
    2022-03-03
  • Java虛擬機(jī)啟動(dòng)過(guò)程探索

    Java虛擬機(jī)啟動(dòng)過(guò)程探索

    當(dāng)我們?cè)诰帉?xiě)Java應(yīng)用的時(shí)候,很少會(huì)注意Java程序是如何被運(yùn)行的,如何被操作系統(tǒng)管理和調(diào)度的,帶著好奇心,探索一下Java虛擬機(jī)啟動(dòng)過(guò)程
    2022-05-05
  • springboot排除某些自動(dòng)配置的操作方法

    springboot排除某些自動(dòng)配置的操作方法

    Spring Boot 提供的自動(dòng)配置非常強(qiáng)大,某些情況下,自動(dòng)配置的功能可能不符合我們的需求,需要我們自定義配置,這個(gè)時(shí)候就需要排除/禁用Spring Boot 某些類(lèi)的自動(dòng)化配置了,本文給大家介紹springboot排除某些自動(dòng)配置的方法,感興趣的朋友一起看看吧
    2023-08-08
  • 什么是Base64以及在Java中如何使用Base64編碼

    什么是Base64以及在Java中如何使用Base64編碼

    這篇文章主要介紹了什么是Base64以及在Java中如何使用Base64編碼的相關(guān)資料,文中包括Base64編碼的基本原理、應(yīng)用、Java實(shí)現(xiàn)、URL安全編碼以及在JavaScript中的使用方法,需要的朋友可以參考下
    2024-12-12
  • Java?中的?clone(?)?和?new哪個(gè)效率更高

    Java?中的?clone(?)?和?new哪個(gè)效率更高

    很多朋友不太清楚clone()和new那個(gè)更快?針對(duì)這個(gè)問(wèn)題我百度了好多資料,最終小編總結(jié)下關(guān)于Java?中的?clone(?)?和?new哪個(gè)效率更高的問(wèn)題,感興趣的朋友跟隨小編一起看看吧
    2021-12-12
  • Java實(shí)現(xiàn)ftp上傳下載、刪除文件及在ftp服務(wù)器上傳文件夾的方法

    Java實(shí)現(xiàn)ftp上傳下載、刪除文件及在ftp服務(wù)器上傳文件夾的方法

    這篇文章主要介紹了Java實(shí)現(xiàn)ftp上傳下載、刪除文件及在ftp服務(wù)器上傳文件夾的方法,需要的朋友可以參考下
    2015-11-11
  • Messges Queue消息隊(duì)列詳解

    Messges Queue消息隊(duì)列詳解

    這篇文章主要介紹了Messges Queue消息隊(duì)列詳解,消息隊(duì)列一般簡(jiǎn)稱為 MQ,是指利用高效可靠的消息傳遞機(jī)制進(jìn)行與平臺(tái)無(wú)關(guān)的數(shù)據(jù)交流,并基于數(shù)據(jù)通信來(lái)進(jìn)行分布式系統(tǒng)的集成,是在消息的傳輸過(guò)程中保存消息的容器,需要的朋友可以參考下
    2023-07-07

最新評(píng)論

洪湖市| 建水县| 庆城县| 安丘市| 舞钢市| 邯郸县| 平江县| 珲春市| 大竹县| 辽源市| 台江县| 稷山县| 新安县| 漳浦县| 四会市| 白河县| 临猗县| 冀州市| 胶南市| 昭通市| 芷江| 樟树市| 德钦县| 朔州市| 防城港市| 房山区| 龙泉市| 无为县| 乐昌市| 贺州市| 垫江县| 兴义市| 水城县| 修文县| 大石桥市| 上虞市| 明光市| 日喀则市| 安泽县| 沈丘县| 梁山县|