新聞中心
這篇文章給大家分享的是有關(guān)JMM指令重排序的示例分析的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
成都創(chuàng)新互聯(lián)10多年成都企業(yè)網(wǎng)站建設(shè)服務(wù);為您提供網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)頁設(shè)計及高端網(wǎng)站定制服務(wù),成都企業(yè)網(wǎng)站建設(shè)及推廣,對成都宣傳片制作等多個行業(yè)擁有多年的營銷推廣經(jīng)驗的網(wǎng)站建設(shè)公司。
一、指令為什么要重排序?
在計算機(jī)系統(tǒng)中,指令為了更快的完成結(jié)果,會根據(jù)邏輯關(guān)系、指令大小進(jìn)行重排序,以達(dá)到超流水線的效果,但在代碼執(zhí)行完后,保證結(jié)果輸出是一致的。
在JVM中又兩條原則:
as-if -serials 這個規(guī)則適用于單線程,默認(rèn)自動處理
happens-before 這個規(guī)則適用于多線程,當(dāng)然也要通過相應(yīng)的鎖和關(guān)鍵字來實現(xiàn)
二、JMM(Java Memory Manager)指令重排序如何實現(xiàn)的呢?
我們知道,JIT會根據(jù)CPU架構(gòu)編譯合適的代碼去執(zhí)行,因此,在匯編層面討論反而不見得能統(tǒng)一意見,比如X86,他只有l(wèi)ock來實現(xiàn),其他CPU架構(gòu)則是LoadLoad
,StoreStore
,LoadStore
,StoreLoad
。
實際上我們還是在JIT層面思考比較容易理解,這里我們定義一個類
(注意:這個類未必和JIT實際效果一致,取決于JIT激進(jìn)程度)
package com.apptest; public class VolatileWatcher { private String A; private String B; private String C; private String D; private String E; public void run() { String a = "a"; String b = "b"; String c = "c"; String d = "d"; String e = "e"; A = a; B = b; C = c; D = d; E = e; System.out.println(A); System.out.println(B); System.out.println(C); System.out.println(D); System.out.println(E); } }
那么如果重排序,效果可能如下
package com.apptest; public class VolatileWatcher { private String A; private String B; private String C; private String D; private String E; public void run() { String a = "a"; String b = "b"; String c = "c"; String d = "d"; String e = "e"; A = a; System.out.println(A); B = b; System.out.println(B); C = c; System.out.println(C); D = d; System.out.println(D); E = e; System.out.println(E); } }
這是JIT可能重排序后的結(jié)果,實際上寄存器、CPU也會進(jìn)行重排序,但最終也會保證內(nèi)存一致性。
三、如何禁止指令重排序呢?
Java中,禁止指令重排序的關(guān)鍵字只有2個,一個是volatile、另一個是final,某種成都上,也能說明final和volatile不能同時修飾一個變量的原因,因為他們的功能有些重疊。
3 .1 要不要禁止
首先要確定要不要禁止重排序,重排序往往在多線程中出現(xiàn)問題,如果程序在串行執(zhí)行,完全沒有必要,因此,單線程中的引用類型(不包括常量和字符串常量,這些類型引用建議加final)建議不要加final和volatile,當(dāng)然,final需要考慮深入一些,因為有時我們需要做一些強(qiáng)約束,但總體來說能不加就不加。
3.2 原理
多線程中,維護(hù)hanpens-before原則不是JVM自身就能處理的,還需要在代碼層面進(jìn)行相應(yīng)的指示,此外,相應(yīng)的JIT會將指示編譯成指令,讓寄存器和CPU也遵守。
指令重排序的達(dá)到的最終效果
讀操作
寫操作
3.3 效果演示
回到文章開頭,我們給VolatileWatcher的C變量添加volatile修飾,那么JIT重排序后的結(jié)果可能是如下情況
public class VolatileWatcher { private String A; private String B; private volatile String C; private String D; private String E; public void run() { String a = "a"; String b = "b"; String c = "c"; String d = "d"; String e = "e"; B = b; A = a; C = c; //寫屏障 E = e; D = d; System.out.println(A); System.out.println(B); System.out.println(C); //讀屏障 System.out.println(D); System.out.println(E); } }
我們發(fā)現(xiàn),A、B不能跨越C變量的寫屏障往下重排序,但是屏障上方的A、B之間也是可以重排序的,E、D不能跨越讀屏障,但是E、D可以在讀屏障和寫屏障之間排序。(這里故意寫成A,B,E,D在屏障外順序變化,目的主要是為了說明屏障的作用,具體看實際效果)
四、final和volatile的區(qū)別?
我們知道,final修飾的是只讀變量,有很強(qiáng)的“只讀”約束性,所有final修飾的變量都需要在構(gòu)造方法中初始化(類外部final字段的最終會插入到super(...)之后 ),而volatile一般用于多線程變量的讀寫,但他們都具備實現(xiàn)內(nèi)存屏障的能力,可以說,都能實現(xiàn)禁止重排序。
不同點(diǎn):
final字段的寫操作,只作用于構(gòu)造函數(shù)。
final字段的讀操作,讀取和volatile基本一樣
感謝各位的閱讀!關(guān)于“JMM指令重排序的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
新聞名稱:JMM指令重排序的示例分析
鏈接地址:http://www.ef60e0e.cn/article/pgghgc.html