日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

一個 println 竟然比 volatile 還好使?

來源: 責編: 時間:2023-10-08 09:59:58 301觀看
導讀前兩天一個小伙伴突然找我求助,說準備換個坑,最近在系統復習多線程知識,但遇到了一個刷新認知的問題……小伙伴:Effective JAVA 里的并發章節里,有一段關于可見性的描述。下面這段代碼會出現死循環,這個我能理解,JMM 內存模

前兩天一個小伙伴突然找我求助,說準備換個坑,最近在系統復習多線程知識,但遇到了一個刷新認知的問題……lv128資訊網——每日最新資訊28at.com

小伙伴:Effective JAVA 里的并發章節里,有一段關于可見性的描述。下面這段代碼會出現死循環,這個我能理解,JMM 內存模型嘛,JMM 不保證 stopRequested 的修改能被及時的觀測到。lv128資訊網——每日最新資訊28at.com

static boolean stopRequested = false;public static void main(String[] args) throws InterruptedException {    Thread backgroundThread = new Thread(() -> {        int i = 0;        while (!stopRequested) {            i++;        }    }) ;    backgroundThread.start();    TimeUnit.MICROSECONDS.sleep(10);    stopRequested = true ;}

但奇怪的是在我加了一行打印之后,就不會出現死循環了!難道我一行 println 能比 volatile 還好使啊?這倆也沒關系啊lv128資訊網——每日最新資訊28at.com

static boolean stopRequested = false;public static void main(String[] args) throws InterruptedException {    Thread backgroundThread = new Thread(() -> {        int i = 0;        while (!stopRequested) {                        // 加上一行打印,循環就能退出了!        	System.out.println(i++);        }    }) ;    backgroundThread.start();    TimeUnit.MICROSECONDS.sleep(10);    stopRequested = true ;}

我:小伙子八股文背的挺熟啊,JMM 張口就來。lv128資訊網——每日最新資訊28at.com

我:這個……其實是 JIT 干的好事,導致你的循環無法退出。JMM 只是一個邏輯上的內存模型規范,JIT可以根據JMM的規范來進行優化。lv128資訊網——每日最新資訊28at.com

比如你第一個例子里,你用-Xint禁用 JIT,就可以退出死循環了,不信你試試?lv128資訊網——每日最新資訊28at.com

小伙伴:WK,真的可以,加上 -Xint 循環就退出了,好神奇!JIT 是個啥啊?還能有這種功效?lv128資訊網——每日最新資訊28at.com

JIT(Just-in-Time) 的優化

眾所周知,JAVA 為了實現跨平臺,增加了一層 JVM,不同平臺的 JVM 負責解釋執行字節碼文件。雖然有一層解釋會影響效率,但好處是跨平臺,字節碼文件是平臺無關的。lv128資訊網——每日最新資訊28at.com

lv128資訊網——每日最新資訊28at.com

在 JAVA 1.2 之后,增加了 即時編譯(Just-in-Time Compilation,簡稱 JIT) 的機制,在運行時可以將執行次數較多的熱點代碼編譯為機器碼,這樣就不需要 JVM 再解釋一遍了,可以直接執行,增加運行效率。lv128資訊網——每日最新資訊28at.com

lv128資訊網——每日最新資訊28at.com

但 JIT 編譯器在編譯字節碼時,可不僅僅是簡單的直接將字節碼翻譯成機器碼,它在編譯的同時還會做很多優化,比如循環展開、方法內聯等等……
lv128資訊網——每日最新資訊28at.com

這個問題出現的原因,就是因為 JIT 編譯器的優化技術之一 - 表達式提升(expression hoisting) 導致的。lv128資訊網——每日最新資訊28at.com

表達式提升(expression hoisting)

先來看個例子,在這個 hoisting 方法中,for 循環里每次都會定義一個變量 y,然后通過將 x*y 的結果存儲在一個 result 變量中,然后使用這個變量進行各種操作lv128資訊網——每日最新資訊28at.com

public void hoisting(int x) {	for (int i = 0; i < 1000; i = i + 1) {		// 循環不變的計算 		int y = 654;		int result = x * y;				// ...... 基于這個 result 變量的各種操作	}}

但是這個例子里,result 的結果是固定的,并不會跟著循環而更新。所以完全可以將 result 的計算提取到循環之外,這樣就不用每次計算了。JIT 分析后會對這段代碼進行優化,進行表達式提升的操作:lv128資訊網——每日最新資訊28at.com

public void hoisting(int x) {	int y = 654;	int result = x * y;    	for (int i = 0; i < 1000; i = i + 1) {			// ...... 基于這個 result 變量的各種操作	}}

這樣一來,result 不用每次計算了,而且也完全不影響執行結果,大大提升了執行效率。lv128資訊網——每日最新資訊28at.com

注意,編譯器更喜歡局部變量,而不是靜態變量或者成員變量;因為靜態變量是“逃逸在外的”,多個線程都可以訪問到,而局部變量是線程私有的,不會被其他線程訪問和修改。lv128資訊網——每日最新資訊28at.com

編譯器在處理靜態變量/成員變量時,會比較保守,不會輕易優化。lv128資訊網——每日最新資訊28at.com

像你問題里的這個例子中,stopRequested就是個靜態變量,編譯器本不應該對其進行優化處理;lv128資訊網——每日最新資訊28at.com

static boolean stopRequested = false;// 靜態變量public static void main(String[] args) throws InterruptedException {    Thread backgroundThread = new Thread(() -> {        int i = 0;        while (!stopRequested) {			// leaf method            i++;        }    }) ;    backgroundThread.start();    TimeUnit.MICROSECONDS.sleep(10);    stopRequested = true ;}

但由于你這個循環是個 leaf method,即沒有調用任何方法,所以在循環之中不會有其他線程會觀測到stopRequested值的變化。那么編譯器就冒進的進行了表達式提升的操作,將stopRequested提升到表達式之外,作為循環不變量(loop invariant)處理:lv128資訊網——每日最新資訊28at.com

int i = 0;boolean hoistedStopRequested = stopRequested;// 將stopRequested 提升為局部變量while (!hoistedStopRequested) {    	i++;}

這樣一來,最后將 stopRequested賦值為 true 的操作,影響不了提升的hoistedStopRequested的值,自然就無法影響循環的執行了,最終導致無法退出。lv128資訊網——每日最新資訊28at.com

至于你增加了 println 之后,循環就可以退出的問題。是因為你這行 println 代碼影響了編譯器的優化。println 方法由于最終會調用
FileOutputStream.writeBytes 這個 native 方法,所以無法被內聯優化(inling)。而未被內斂的方法調用從編譯器的角度看是一個“full memory kill”,也就是說 副作用不明 、必須對內存的讀寫操作做保守處理。lv128資訊網——每日最新資訊28at.com

在這個例子里,下一輪循環的 stopRequested 讀取操作按順序要發生在上一輪循環的 println 之后。這里“保守處理”為:就算上一輪我已經讀取了 stopRequested 的值,由于經過了一個副作用不明的地方,再到下一次訪問就必須重新讀取了。lv128資訊網——每日最新資訊28at.com

所以在你增加了 prinltln 之后,JIT 由于要保守處理,重新讀取,自然就不能做上面的表達式提升優化了。lv128資訊網——每日最新資訊28at.com

以上對表達式提升的解釋,總結摘抄自 R大的知乎回答。R大,行走的 JVM Wiki!
lv128資訊網——每日最新資訊28at.com

我:“這下明白了吧,這都是 JIT 干的好事,你要是禁用 JIT 就沒這問題了”lv128資訊網——每日最新資訊28at.com

小伙伴:“WK,一個簡單的 for 循環也太多機制了,沒想到 JIT 這么智能,也沒想到 R 大這么”lv128資訊網——每日最新資訊28at.com

小伙伴:“那 JIT 一定很多優化機制吧,除了這個表達式提升還有啥?”lv128資訊網——每日最新資訊28at.com

我:我也不是搞編譯器的……哪了解這么多,就知道一些常用的,簡單給你說說吧lv128資訊網——每日最新資訊28at.com

表達式下沉(expression sinking)

和表達式提升類似的,還有個表達式下沉的優化,比如下面這段代碼:lv128資訊網——每日最新資訊28at.com

public void sinking(int i) {	int result = 543 * i;	if (i % 2 == 0) {		// 使用 result 值的一些邏輯代碼	} else {		// 一些不使用 result 的值的邏輯代碼	}}

由于在 else 分支里,并沒有使用 result 的值,可每次不管什么分支都會先計算 result,這就沒必要了。JIT 會把 result 的計算表達式移動到 if 分支里,這樣就避免了每次對 result 的計算,這個操作就叫表達式下沉:lv128資訊網——每日最新資訊28at.com

public void sinking(int i) {	if (i % 2 == 0) {		int result = 543 * i;		// 使用 result 值的一些邏輯代碼	} else {		// 一些不使用 result 的值的邏輯代碼	}}

JIT 還有那些常見優化?

除了上面介紹的表達式提升/表達式下沉以外,還有一些常見的編譯器優化機制。lv128資訊網——每日最新資訊28at.com

循環展開(Loop unwinding/loop unrolling)

下面這個 for 循環,一共要循環 10w 次,每次都需要檢查條件。lv128資訊網——每日最新資訊28at.com

for (int i = 0; i < 100000; i++) {    delete(i);}

在編譯器的優化后,會刪除一定的循環次數,從而降低索引遞增和條件檢查操作而引起的開銷:lv128資訊網——每日最新資訊28at.com

for (int i = 0; i < 20000; i+=5) {    delete(i);    delete(i + 1);    delete(i + 2);    delete(i + 3);    delete(i + 4);}

除了循環展開,循環還有一些優化機制,比如循環剝離、循環交換、循環分裂、循環合并……lv128資訊網——每日最新資訊28at.com

內聯優化(Inling)

JVM 的方法調用是個棧的模型,每次方法調用都需要一個壓棧(push)和出棧(pop)的操作,編譯器也會對調用模型進行優化,將一些方法的調用進行內聯。
lv128資訊網——每日最新資訊28at.com

內聯就是抽取要調用的方法體代碼,到當前方法中直接執行,這樣就可以避免一次壓棧出棧的操作,提升執行效率。比如下面這個方法:lv128資訊網——每日最新資訊28at.com

public  void inline(){	int a = 5;    int b = 10;    int c = calculate(a, b);        // 使用 c 處理……}public int calculate(int a, int b){	return a + b;}

在編譯器內聯優化后,會將 calculate 的方法體抽取到 inline 方法中,直接執行,而不用進行方法調用:lv128資訊網——每日最新資訊28at.com

public  void inline(){	int a = 5;    int b = 10;    int c = a + b;        // 使用 c 處理……}

不過這個內聯優化是有一些限制的,比如 native 的方法就不能內聯優化lv128資訊網——每日最新資訊28at.com

提前置空

來先看一個例子,在這個例子中 was finalized! 會在 done.之前輸出,這個也是因為 JIT 的優化導致的。lv128資訊網——每日最新資訊28at.com

class A {    // 對象被回收前,會觸發 finalize    @Override protected void finalize() {        System.out.println(this + " was finalized!");    }    public static void main(String[] args) throws InterruptedException {        A a = new A();        System.out.println("Created " + a);        for (int i = 0; i < 1_000_000_000; i++) {            if (i % 1_000_00 == 0)                System.gc();        }        System.out.println("done.");    }}//打印結果Created A@1be6f5c3A@1be6f5c3 was finalized!//finalize方法輸出done.

從例子中可以看到,如果 a 在循環完成后已經不再使用了,則會出現先執行finalize的情況;雖然從對象作用域來說,方法沒有執行完,棧幀并沒有出棧,但是還是會被提前執行。lv128資訊網——每日最新資訊28at.com

這就是因為 JIT 認為 a 對象在循環內和循環后都不會在使用,所以提前給它置空了,幫助 GC 回收;如果禁用 JIT,那就不會出現這個問題。lv128資訊網——每日最新資訊28at.com

這個提前回收的機制,還是有點風險的,在某些場景下可能會引起 BUG……lv128資訊網——每日最新資訊28at.com

HotSpot VM JIT 的各種優化項

上面只是介紹了幾個簡單常用的編譯優化機制,JVM JIT 更多的優化機制可以參考下面這個圖。這是 OpenJDK 文檔中提供的一個 pdf 材料,里面列出了 HotSpot JVM 的各種優化機制,相當多……lv128資訊網——每日最新資訊28at.com

lv128資訊網——每日最新資訊28at.com

如何避免因 JIT 導致的問題?

小伙伴:“JIT 這么多優化機制,很容易出問題啊,我平時寫代碼要怎么避開這些呢”lv128資訊網——每日最新資訊28at.com

平時在編碼的時候,不用刻意的去關心 JIT 的優化,就比如上面那個 println 問題,JMM 本來就不保證修改對其他線程可見,如果按照規范去加鎖或者用 volatile 修飾,根本就不會有這種問題。lv128資訊網——每日最新資訊28at.com

而那個提前置空導致的問題,出現的幾率也很低,只要你規范寫代碼基本不會遇到的。lv128資訊網——每日最新資訊28at.com

我:所以,這不是 JIT 的鍋,是你的……lv128資訊網——每日最新資訊28at.com

小伙伴:“懂了,你這是說我菜,說我代碼寫的屎啊……”lv128資訊網——每日最新資訊28at.com

總結

在日常編碼過程中,不用刻意的猜測 JIT 的優化機制,JVM 也不會完整的告訴你所有的優化。而且這種東西不同版本效果不一樣,就算搞明白了一個機制,可能到下個版本就會完全不一樣。lv128資訊網——每日最新資訊28at.com

所以,如果不是搞編譯器開發的話,JIT 相關的編譯知識,作為一個知識儲備就好。lv128資訊網——每日最新資訊28at.com

也不用去猜測 JIT 到底會怎么優化你的代碼,你(可能)猜不準……lv128資訊網——每日最新資訊28at.com

本故事純屬瞎編,請勿隨意對號入座lv128資訊網——每日最新資訊28at.com

參考

  • JSR-133 Java Memory Model and Thread Specification 1.0 Proposed Final Draft
  • Oracle JVM Just-in-Time Compiler (JIT)
  • JVM JIT-compiler overview - Vladimir Ivanov HotSpot JVM Compiler Oracle Corp.
  • JVM JIT optimization techniques - part 2
  • The Java platform - WikiBook
  • R 大的知乎百科

一點補充

可能部分讀者大佬們會認為是 sync 導致的問題,下面是稍加改造后的 sync 例子,結果是仍然無法退出死循環……lv128資訊網——每日最新資訊28at.com

public class HoistingTest {	static boolean stopRequested = false;	public static void main(String[] args) throws InterruptedException {		Thread backgroundThread = new Thread(() -> {			int i = 0;			while (!stopRequested) {				// 加上一行打印,循環就能退出了!//				System.out.println(i++);				new HoistingTest().test();			}		}) ;		backgroundThread.start();		TimeUnit.SECONDS.sleep(5);		stopRequested = true ;	}	Object lock = new Object();	private  void test(){		synchronized (lock){}	}}

再升級下,把 test 方法,也加上 sync,結果還是無法退出死循環……lv128資訊網——每日最新資訊28at.com

Object lock = new Object();private synchronized void test(){        synchronized (lock){}}

但我只是想說,這個問題的關鍵是 jit 的優化導致的問題。jmm 只是規范,而 jit 的優化機制,也會遵循 jmm 的規范。lv128資訊網——每日最新資訊28at.com

不過 jmm 并沒有說 sync 會影響 jit 之類的,可就算 sync 會影響那又怎么樣呢……并不是關鍵點lv128資訊網——每日最新資訊28at.com

結合 R大 的解釋,編譯器對靜態變量更敏感,如果把上面的 lock 對象修改成 static 的,循環又可以退出了……lv128資訊網——每日最新資訊28at.com

那如果不加 static ,把 sync 換成 unsafe.pageSize()呢?結果是循環還是可以退出……lv128資訊網——每日最新資訊28at.com

所以,本文的重點是描述 jit 的影響,而不是各種會影響 jit 的動作。影響 jit 的可能性會非常多,而且不同的vm甚至不同的版本表現都會有所不同,我們并不需要去摸清這個機制,也沒法摸清(畢竟不是做編譯器的,就是是做編譯器,也不一定是 HotSpot……)lv128資訊網——每日最新資訊28at.com

作者:京東保險 蔣信lv128資訊網——每日最新資訊28at.com

來源:京東云開發者社區 lv128資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-12432-0.html一個 println 竟然比 volatile 還好使?

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 圖形編輯器開發:快捷鍵的管理

下一篇: 如何實現并部署自己的Npm解析服務

標簽:
  • 熱門焦點
  • Redmi Buds 4開箱簡評:才199還有降噪 可以無腦入

    在上個月舉辦的Redmi Note11T Pro系列新機發布會上,除了兩款手機新品之外,Redmi還帶來了兩款TWS真無線藍牙耳機產品,Redmi Buds 4和Redmi Buds 4 Pro,此前我們在Redmi Note11T
  • JavaScript 混淆及反混淆代碼工具

    介紹在我們開始學習反混淆之前,我們首先要了解一下代碼混淆。如果不了解代碼是如何混淆的,我們可能無法成功對代碼進行反混淆,尤其是使用自定義混淆器對其進行混淆時。什么是混
  • 如何通過Python線程池實現異步編程?

    線程池的概念和基本原理線程池是一種并發處理機制,它可以在程序啟動時創建一組線程,并將它們置于等待任務的狀態。當任務到達時,線程池中的某個線程會被喚醒并執行任務,執行完任
  • 騰訊蓋樓,字節拆墻

    來源 | 光子星球撰文 | 吳坤諺編輯 | 吳先之&ldquo;想重溫暴刷深淵、30+技能搭配暴搓到爽的游戲體驗嗎?一起上晶核,即刻暴打!&rdquo;曾憑借直播騰訊旗下代理格斗游戲《DNF》一
  • 消費結構調整丨巨頭低價博弈,拼多多還卷得動嗎?

    來源:征探財經作者:陳香羽隨著流量紅利的退潮,電商的存量博弈越來越明顯。曾經主攻中高端與品質的淘寶天貓、京東重拾&ldquo;低價&rdquo;口號。而過去與他們錯位競爭的拼多多,靠
  • 花7萬退貨退款無門:誰在縱容淘寶珠寶商家造假?

    來源:極點商業作者:楊銘在淘寶購買珠寶玉石后,因為保證金不夠賠付,店鋪關閉,退貨退款難、維權無門的比比皆是。&ldquo;提供相關產品鑒定證書,支持全國復檢,可以30天無理由退換貨。&
  • 重估百度丨大模型,能撐起百度的“今天”嗎?

    自象限原創 作者|程心 羅輯2023年之前,對于自己的&ldquo;今天&rdquo;,百度也很迷茫。&ldquo;新業務到 2022 年底還是 0,希望 2023 年出來一個 1。&rdquo;這是2022年底,李彥宏
  • 華為發布HarmonyOS 4:更好玩、更流暢、更安全

    在8月4日的華為開發者大會2023(HDC.Together)大會上,HarmonyOS 4正式發布。自2019年發布以來,HarmonyOS一直以用戶為中心,經歷四年多的發展HarmonyOS已
  • AI芯片初創公司Tenstorrent獲三星和現代1億美元投資

    Tenstorrent是一家由芯片行業資深人士Jim Keller領導的加拿大初創公司,專注于開發人工智能芯片,該公司周三表示,已經從現代汽車集團和三星投資基金等
Top 主站蜘蛛池模板: 大荔县| 阳新县| 兴文县| 鄢陵县| 新巴尔虎左旗| 宜川县| 恩施市| 漳州市| 肇州县| 太白县| 西丰县| 广南县| 龙川县| 沁源县| 上饶市| 吴旗县| 安岳县| 昌黎县| 聊城市| 梓潼县| 宣威市| 鄂托克旗| 米易县| 自贡市| 年辖:市辖区| 甘肃省| 剑川县| 岑巩县| 东方市| 孝昌县| 衢州市| 监利县| 白山市| 西盟| 武冈市| 五莲县| 呼和浩特市| 新竹县| 措勤县| 大田县| 班戈县|