這篇文章主要介紹“怎么理解Java詭異并發(fā)中的有序性”,在日常操作中,相信很多人在怎么理解Java詭異并發(fā)中的有序性問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”怎么理解Java詭異并發(fā)中的有序性”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于網(wǎng)站制作、網(wǎng)站建設(shè)、三水網(wǎng)絡(luò)推廣、微信小程序開(kāi)發(fā)、三水網(wǎng)絡(luò)營(yíng)銷、三水企業(yè)策劃、三水品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供三水建站搭建服務(wù),24小時(shí)服務(wù)熱線:18982081108,官方網(wǎng)址:www.rwnh.cn
序、有序性的闡述
有序性為什么要探討?因?yàn)?Java 是面向?qū)ο缶幊痰?,關(guān)注的只是最終結(jié)果,很少去研究其具體執(zhí)行過(guò)程?正如上一篇文章在介紹可見(jiàn)性時(shí)描述的一樣,操作系統(tǒng)為了提升性能,將 Java 語(yǔ)言轉(zhuǎn)換成機(jī)器語(yǔ)言的時(shí)候,吩咐編譯器對(duì)語(yǔ)句的執(zhí)行順序進(jìn)行了一定的修改,以促使系統(tǒng)性能達(dá)到最優(yōu)。所以在很多情況下,訪問(wèn)一個(gè)程序變量(對(duì)象實(shí)例字段,類靜態(tài)字段和數(shù)組元素)可能會(huì)使用不同的順序執(zhí)行,而不是程序語(yǔ)義所指定的順序執(zhí)行。
正如大家所熟知那樣,Java語(yǔ)言是運(yùn)行在 Java 自帶的 JVM(Java Virtual Machine) 環(huán)境中,在JVM環(huán)境中源代碼(.class)的執(zhí)行順序與程序的執(zhí)行順序(runtime)不一致,或者程序執(zhí)行順序與編譯器執(zhí)行順序不一致的情況下,我們就稱程序執(zhí)行過(guò)程中發(fā)生了重排序。
而編譯器的這種修改是自以為能保證最終運(yùn)行結(jié)果!因?yàn)樵趩魏藭r(shí)代完全沒(méi)問(wèn)題;但是隨著多核時(shí)代的到來(lái),多線程的環(huán)境下,這種優(yōu)化碰上線程切換就大大的增加了事故的出現(xiàn)幾率!
好心辦了壞事!
也就是說(shuō),有序性 指的是在代碼順序結(jié)構(gòu)中,我們可以直觀的指定代碼的執(zhí)行順序, 即從上到下按序執(zhí)行。但編譯器和CPU處理器會(huì)根據(jù)自己的決策,對(duì)代碼的執(zhí)行順序進(jìn)行重新排序。優(yōu)化指令的執(zhí)行順序,提升程序的性能和執(zhí)行速度,使語(yǔ)句執(zhí)行順序發(fā)生改變,出現(xiàn)重排序,但最終結(jié)果看起來(lái)沒(méi)什么變化(單核)。
有序性問(wèn)題 指的是在多線程環(huán)境下(多核),由于執(zhí)行語(yǔ)句重排序后,重排序的這一部分沒(méi)有一起執(zhí)行完,就切換到了其它線程,導(dǎo)致的結(jié)果與預(yù)期不符的問(wèn)題。這就是編譯器的編譯優(yōu)化給并發(fā)編程帶來(lái)的程序有序性問(wèn)題。
用圖示就是:
阿粉小結(jié):編譯優(yōu)化最終導(dǎo)致了有序性問(wèn)題。
一、導(dǎo)致有序性的原因:
如果一個(gè)線程寫(xiě)入值到字段 a,然后寫(xiě)入值到字段 b ,而且b的值不依賴于 a 的值,那么,處理器就能夠自由的調(diào)整它們的執(zhí)行順序,而且緩沖區(qū)能夠在 a 之前刷新b的值到主內(nèi)存。此時(shí)就可能會(huì)出現(xiàn)有序性問(wèn)題。
例子:
1import java.time.LocalDateTime; 2 3/** 4 * @author :mmzsblog 5 * @description:并發(fā)中的有序性問(wèn)題 6 * @date :2020年2月26日 15:22:05 7 */ 8public class OrderlyDemo { 9 10 static int value = 1; 11 private static boolean flag = false; 12 13 public static void main(String[] args) throws InterruptedException { 14 for (int i = 0; i < 199; i++) { 15 value = 1; 16 flag = false; 17 Thread thread1 = new DisplayThread(); 18 Thread thread2 = new CountThread(); 19 thread1.start(); 20 thread2.start(); 21 System.out.println("========================================================="); 22 Thread.sleep(6000); 23 } 24 } 25 26 static class DisplayThread extends Thread { 27 @Override 28 public void run() { 29 System.out.println(Thread.currentThread().getName() + " DisplayThread begin, time:" + LocalDateTime.now()); 30 value = 1024; 31 System.out.println(Thread.currentThread().getName() + " change flag, time:" + LocalDateTime.now()); 32 flag = true; 33 System.out.println(Thread.currentThread().getName() + " DisplayThread end, time:" + LocalDateTime.now()); 34 } 35 } 36 37 static class CountThread extends Thread { 38 @Override 39 public void run() { 40 if (flag) { 41 System.out.println(Thread.currentThread().getName() + " value的值是:" + value + ", time:" + LocalDateTime.now()); 42 System.out.println(Thread.currentThread().getName() + " CountThread flag is true, time:" + LocalDateTime.now()); 43 } else { 44 System.out.println(Thread.currentThread().getName() + " value的值是:" + value + ", time:" + LocalDateTime.now()); 45 System.out.println(Thread.currentThread().getName() + " CountThread flag is false, time:" + LocalDateTime.now()); 46 } 47 } 48 } 49}
運(yùn)行結(jié)果:
從打印的可以看出:在 DisplayThread 線程執(zhí)行的時(shí)候肯定是發(fā)生了重排序,導(dǎo)致先為 flag 賦值,然后切換到 CountThread 線程,這才出現(xiàn)了打印的 value 值是1,falg 值是 true 的情況,再為 value 賦值;不過(guò)出現(xiàn)這種情況的原因就是這兩個(gè)賦值語(yǔ)句之間沒(méi)有聯(lián)系,所以編譯器在進(jìn)行代碼編譯的時(shí)候就可能進(jìn)行指令重排序。
用圖示,則為:
二、如何解決有序性
2.1、volatile
volatile 的底層是使用內(nèi)存屏障來(lái)保證有序性的(讓一個(gè) CPU 緩存中的狀態(tài)(變量)對(duì)其他 CPU 緩存可見(jiàn)的一種技術(shù))。
volatile 變量有條規(guī)則是指對(duì)一個(gè) volatile 變量的寫(xiě)操作, Happens-Before于后續(xù)對(duì)這個(gè) volatile 變量的讀操作。并且這個(gè)規(guī)則具有傳遞性,也就是說(shuō):
此時(shí),我們定義變量 flag 時(shí)使用 volatile 關(guān)鍵字修飾,如:
1 private static volatile boolean flag = false;
此時(shí),變量的含義是這樣子的:
也就是說(shuō),只要讀取到 flag=true; 就能讀取到 value=1024;否則就是讀取到flag=false; 和 value=1 的還沒(méi)被修改過(guò)的初始狀態(tài);
但也有可能會(huì)出現(xiàn)線程切換帶來(lái)的原子性問(wèn)題,就是讀取到 flag=false; 而value=1024 的情況;看過(guò)上一篇講述[原子性]()的文章的小伙伴,可能就立馬明白了,這是線程切換導(dǎo)致的。
2.2、加鎖
此處我們直接采用Java語(yǔ)言內(nèi)置的關(guān)鍵字 synchronized,為可能會(huì)重排序的部分加鎖,讓其在宏觀上或者說(shuō)執(zhí)行結(jié)果上看起來(lái)沒(méi)有發(fā)生重排序。
代碼修改也很簡(jiǎn)單,只需用 synchronized 關(guān)鍵字修飾 run 方法即可,代碼如下:
1 public synchronized void run() { 2 value = 1024; 3 flag = true; 4 }
同理,既然是加鎖,當(dāng)然也可以使用 Lock 加鎖,但 Lock 必須要用戶去手動(dòng)釋放鎖,如果沒(méi)有主動(dòng)釋放鎖,就有可能導(dǎo)致出現(xiàn)死鎖現(xiàn)象。這點(diǎn)在使用的時(shí)候一定要注意!
使用該種方式加鎖也很簡(jiǎn)單,代碼如下:
1 readWriteLock.writeLock().lock(); 2 try { 3 value = 1024; 4 flag = true; 5 } finally { 6 readWriteLock.writeLock().unlock(); 7 }
到此,關(guān)于“怎么理解Java詭異并發(fā)中的有序性”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
名稱欄目:怎么理解Java詭異并發(fā)中的有序性
文章起源:http://www.rwnh.cn/article38/gshdpp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供自適應(yīng)網(wǎng)站、網(wǎng)頁(yè)設(shè)計(jì)公司、App設(shè)計(jì)、虛擬主機(jī)、網(wǎng)站維護(hù)、搜索引擎優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)