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

Volatile關鍵字能保證原子性么?

開發(fā) 前端
如果這時候,面試官不再繼續(xù)深挖下去的話,那么恭喜你,可能這個問題已經(jīng)回答完了,但是如果面試官繼續(xù)往下深挖,為什么會禁止指令重排,什么又是指令重排呢?

說到這個 volatile 這個關鍵字,阿粉覺得看過阿粉文章的,肯定都對這個關鍵字那是非常的熟悉的,因為做Java開發(fā)的,在面試的時候,如果涉及到多線程,那么面試官有不少人會詢問關于 volatile 這個關鍵字的使用,以及他的作用,今天阿粉就來說說這個 volatile 關鍵的的作用,以及他的一些特性。

volatile

volatile 是 Java 中的一個相對來說比較重要的關鍵字,主要就是用來修飾會被不同線程訪問和修改的變量。

而這個變量只能保證兩個特性,一個是保證有序性,另外一個則是保證可見性。

那么什么是有序性,什么又是可見性呢?

有序性

那么什么是有序性呢?

其實程序執(zhí)行的順序按照代碼的先后順序執(zhí)行,禁止進行指令重排序。

看似理所當然,其實并不是這樣,指令重排序是JVM為了優(yōu)化指令,提高程序運行效率,在不影響單線程程序執(zhí)行結(jié)果的前提下,盡可能地提高并行度。

但是在多線程環(huán)境下,有些代碼的順序改變,有可能引發(fā)邏輯上的不正確。

而 volatile 就是因為有這個特性,所以才被大家熟知的。

volatile 又是如何保證有序性的呢?

有很多小伙伴就說,網(wǎng)上說的是 volatile 可以禁止指令指令重排序,這就保證了代碼的程序會嚴格按照代碼的先后順序執(zhí)行。這就保證了有序性。被 volatile 修飾的變量的操作,會嚴格按照代碼順序執(zhí)行,就是說當代碼執(zhí)行到 volatile 修飾的變量時,其前面的代碼一定執(zhí)行完畢,后面的代碼一定沒有執(zhí)行。

如果這時候,面試官不再繼續(xù)深挖下去的話,那么恭喜你,可能這個問題已經(jīng)回答完了,但是如果面試官繼續(xù)往下深挖,為什么會禁止指令重排,什么又是指令重排呢?

在從源碼到指令的執(zhí)行,一般是分成了三種重排,如圖所示:

圖片

我們接下來就得看看 volatile 是如何禁止指令重排的。

我們直接用代碼來進行驗證。

public class ReSortDemo {

int a = 0;
boolean flag = false;

public void mehtod1(){
a = 1;
flag = true;
}

public void method2(){
if(flag){
a = a +1;
System.out.println("最后的值: "+a);
}
}
}

如果有人看到這段代碼,肯定會說,那這段代碼出來的結(jié)果會是什么呢?

有些人說是 2,是的, 如果你只是單線程調(diào)用,那結(jié)果就是 2,但是如果是多線程調(diào)用的時候,最后的輸出結(jié)果不一定是我們想象到的 2,這時就要把兩個變量都設置為 volatile。

如果大家對單例模式了解比較多的話,肯定也是關注過這個 volatile,為什么呢?

大家看看如下代碼:

class Singleton {
// 不是一個原子性操作
//private static Singleton instance;
//改進,Volatile 可以保持可見性,不能保證原子性,由于內(nèi)存屏障,可以保證避免指令重排的現(xiàn)象產(chǎn)生!
private static volatile Singleton instance;

// 構(gòu)造器私有化
private Singleton() {
}

// 提供一個靜態(tài)的公有方法,加入雙重檢查代碼,解決線程安全問題, 同時解決懶加載問題,同時保證了效率, 推薦使用
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

上面的單例模式大家熟悉么?

是的,這就是 **雙重檢查(DCL 懶漢式) **

有人會說,因為有指令重排序的存在,雙端檢索機制也也不一定是線程安全的呀,對呀,所以阿粉用到了 synchronized 關鍵字,讓他變成了線程安全的了。

可見性

其實可見性就是,在多線程環(huán)境中,對共享變量的修改對于其他線程是否立即可見的問題。

那么他的可見性一般都會表現(xiàn)在什么地方呢?用在什么地方呢?

其實在阿粉的感知中,一般用這個變量,很多都是為了保證他的可見性,就比如定義的一個全局變量,在其中有個循環(huán)來判斷這個變量的值,有一個線程修改了這個參數(shù)的時候,這個循環(huán)會停止,跳轉(zhuǎn)到之后去執(zhí)行。

我們來看看沒有使用volatile修飾代碼實現(xiàn):

public class Test {

private static boolean flag = false;

public static void main(String[] args) throws Exception{
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程A開始執(zhí)行:");
for (;;){
if (flag){
System.out.println("跳出循環(huán)");
break;
}
}
}
}).start();
Thread.sleep(100);

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程B開始執(zhí)行");
flag = true;
System.out.println("標識已經(jīng)變更");
}
}).start();
}

}

結(jié)果大家肯定是可想而知。

運行結(jié)果肯定是:

線程A開始執(zhí)行:
線程B開始執(zhí)行
標識已經(jīng)變更

確實,就是這樣的。

圖片

如果我們用 volatile 呢,那么這個代碼的執(zhí)行結(jié)果就會不一樣呢?

我們來試一下:

public class Test {

private static volatile boolean flag = false;

public static void main(String[] args) throws Exception{
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程A開始執(zhí)行:");
for (;;){
if (flag){
System.out.println("跳出循環(huán)");
break;
}
}
}
}).start();
Thread.sleep(100);

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程B開始執(zhí)行");
flag = true;
System.out.println("標識已經(jīng)變更");
}
}).start();
}

這樣我們就能看到另外一個執(zhí)行結(jié)果,在循環(huán)當中的輸出語句是可以被執(zhí)行的。

圖片

也就是說,在線程B 中,我們?nèi)バ薷倪@個被修飾的變量,那么最終,在線程A中,就能順利讀取到我們的數(shù)據(jù)信息了。

是否能夠保證原子性

不能,我們來看一點代碼,被volatile修飾的變量;

public class Test {

// volatile不保證原子性
// 原子性:保證數(shù)據(jù)一致性、完整性
volatile int number = 0;

public void addPlusPlus() {
number++;
}

public static void main(String[] args) {
Test volatileAtomDemo = new Test();
for (int j = 0; j < 20; j++) {
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
volatileAtomDemo.addPlusPlus();
}
}, String.valueOf(j)).start();
}// 后臺默認兩個線程:一個是main線程,一個是gc線程
while (Thread.activeCount() > 2) {
Thread.yield();
}
// 如果volatile保證原子性的話,最終的結(jié)果應該是20000 // 但是每次程序執(zhí)行結(jié)果都不等于20000
System.out.println(Thread.currentThread().getName() +
" final number result = " + volatileAtomDemo.number);
}

}

如果能夠保原子性,那么最終的結(jié)果應該是20000,但是每次的最終結(jié)果并不能保證就是20000,比如:

main final number result = 17114
main final number result = 20000
main final number result = 19317

三次執(zhí)行,都是不同的結(jié)果。

為什么會出現(xiàn)這種呢?這就和number++有關系了。

number++被拆分成3個指令:

  • 執(zhí)行GETFIELD拿到主內(nèi)存中的原始值number。
  • 執(zhí)行IADD進行加1操作。
  • 執(zhí)行PUTFIELD把工作內(nèi)存中的值寫回主內(nèi)存中。

當多個線程并發(fā)執(zhí)行PUTFIELD指令的時候,會出現(xiàn)寫回主內(nèi)存覆蓋問題,所以才會導致最終結(jié)果不為 20000,所以 volatile 不能保證原子性。

所以,你知道怎么回答了么?

責任編輯:武曉燕 來源: Java極客技術
相關推薦

2011-06-14 13:26:27

volatile

2022-06-29 08:05:25

Volatile關鍵字類型

2011-06-21 09:50:51

volatile

2025-06-13 08:00:00

Java并發(fā)編程volatile

2019-09-04 14:14:52

Java編程數(shù)據(jù)

2023-06-26 08:02:34

JSR重排序volatile

2009-06-29 18:14:23

Java多線程volatile關鍵字

2020-07-17 20:15:03

架構(gòu)JMMvolatile

2024-03-15 08:18:25

volatileAtomic關鍵字

2018-01-19 10:43:06

Java面試官volatile關鍵字

2020-11-11 08:45:48

Java

2016-09-19 21:53:30

Java并發(fā)編程解析volatile

2021-08-26 09:50:06

鴻蒙HarmonyOS應用

2011-03-09 14:36:44

synchronizevolatile

2022-06-09 11:20:44

volatile關鍵字

2020-09-24 09:50:07

C語言編程語言

2025-04-22 08:00:00

2009-08-21 14:58:56

C# this關鍵字

2013-01-30 10:12:14

Pythonyield

2018-04-20 15:56:09

Pythonglobal關鍵字
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久九九免费 | 亚洲国产精品久久久久秋霞不卡 | 日韩一二区在线观看 | 国产精品一区视频 | 亚洲第一成年免费网站 | 第四色狠狠| 成人视屏在线观看 | 中文一区 | аⅴ资源新版在线天堂 | 国产欧美精品一区二区色综合 | 精品一二| 黄色av一区 | 午夜精品一区二区三区在线观看 | 日本精品久久 | 天天干免费视频 | 国产女人与拘做受视频 | 亚洲 欧美 激情 另类 校园 | 国产精品久久久久久久久久久久久 | 国产欧美日韩在线观看 | 成人精品视频在线观看 | 久久久福利| 久久久久久久久99 | 九九热在线视频 | 久久福利电影 | 龙珠z国语版在线观看 | 亚洲区一区二 | 天堂中文在线播放 | 日韩视频免费看 | 天天爽综合网 | 91精品国产综合久久国产大片 | 我想看一级黄色毛片 | 一区二区三区视频在线观看 | 成人欧美一区二区三区黑人孕妇 | 亚洲综合在线网 | 精品一区二区三区在线观看 | 欧美日韩一区二区三区四区 | 一区二区在线看 | 久久黄色网 | 日韩在线观看视频一区 | 久久久蜜桃 | 日韩精品一区二区三区免费观看 |