成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

面試突擊:為什么單例一定要加 Volatile?

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

作者 | 磊哥

來(lái)源 | Java面試真題解析(ID:aimianshi666)

轉(zhuǎn)載請(qǐng)聯(lián)系授權(quán)(微信ID:GG_Stone)

單例模式的實(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;
}
}

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

一、volatile 作用

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

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

所謂內(nèi)存可見(jiàn)性問(wèn)題,指的是多個(gè)線(xiàn)程同時(shí)操作一個(gè)變量,其中某個(gè)線(xiàn)程修改了變量的值之后,其他線(xiàn)程感知不到變量的修改,這就是內(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ō)明線(xiàn)程 2 在修改了 flag 變量之后,線(xiàn)程 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)題了。

2、防止指令重排序

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

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

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

二、為什么要用 volatile?

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

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

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

總結(jié)

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

責(zé)任編輯:姜華 來(lái)源: Java面試真題解析
相關(guān)推薦

2022-03-21 07:40:08

線(xiàn)程池Executors方式

2022-05-05 07:38:32

volatilJava并發(fā)

2019-01-29 11:02:30

消息中間件Java互聯(lián)網(wǎng)

2021-03-05 11:02:14

iOS 14.5蘋(píng)果更新

2022-05-23 07:35:15

單例模式懶漢模式靜態(tài)內(nèi)部類(lèi)

2020-12-23 13:29:15

微服務(wù)架構(gòu)面試官

2021-12-03 06:59:23

HashCodeEquals面試

2011-05-10 15:51:34

SEO

2021-12-13 09:10:48

equalshashCodeJava

2022-04-24 09:54:24

ProxyReflect前端

2019-02-14 09:35:15

面試MQ中間件

2022-01-10 13:06:13

微服務(wù)API網(wǎng)關(guān)

2018-08-24 09:02:26

2022-07-27 07:36:01

TCP可靠性

2024-02-22 14:22:17

數(shù)字化轉(zhuǎn)型企業(yè)架構(gòu)

2024-11-13 00:58:28

2023-10-08 07:13:19

equalshashCode哈希表

2018-07-02 08:57:27

碼農(nóng)業(yè)務(wù)程序員

2024-10-10 05:00:00

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 一级毛片在线播放 | 日本一区二区不卡 | 国产精品精品视频一区二区三区 | 婷婷成人在线 | 97精品超碰一区二区三区 | 中文字幕一区二区三区四区五区 | 国产激情偷乱视频一区二区三区 | 久久中文字幕一区 | 久久国产精品一区二区三区 | 精品一区二区三区四区五区 | 久久精品亚洲国产 | 国产清纯白嫩初高生在线播放视频 | 日韩一区三区 | 久久福利网站 | 亚洲国产一区二区三区, | 精品一区二区久久久久久久网站 | 日韩精品在线一区 | 狠狠狠干 | 亚洲成人三区 | 一区二区三区免费 | 在线一区| 欧美日韩一区在线 | 日韩在线观看中文字幕 | 国产一级电影网 | 国产成人精品午夜视频免费 | 欧美一a一片一级一片 | 国产第1页 | 九九热国产精品视频 | 国产在线观看不卡一区二区三区 | 91精品一区二区三区久久久久 | 欧美精品网站 | 午夜一区二区三区视频 | 午夜影视大全 | 亚州精品天堂中文字幕 | 喷潮网站 | 久久亚洲春色中文字幕久久久 | 91精品国产92 | 亚洲三区在线观看 | 国产美女久久久 | 99国产精品久久久久久久 | 欧洲视频一区二区 |