Java中i++的一些問題總結(jié)
參考內(nèi)容:
- 深入理解Java虛擬機(jī)(JVM高級(jí)特性與最佳實(shí)踐) ——周志明老師
- 尚硅谷深入理解JVM教學(xué)視頻——宋紅康老師
在本文展開前,讀者需要了解一些字節(jié)碼有關(guān)的知識(shí),以及JVM虛擬機(jī)棧中棧幀的局部變量表和操作數(shù)棧等知識(shí),筆者在這里只給出一些大概的簡述。
字節(jié)碼
- Java字節(jié)碼對(duì)于虛擬機(jī),就好像匯編語言對(duì)于計(jì)算機(jī),屬于基本執(zhí)行指令。
- 虛擬機(jī)的指令由一個(gè)字節(jié)長度的、代表著某種特定操作含義的數(shù)字(稱為操作碼,Opcode)以及跟隨其后的零至多個(gè)代表此操作所需參數(shù)(稱為操作數(shù),Operands)而構(gòu)成。由于Java虛擬機(jī)采用面向操作數(shù)棧而不是寄存器的結(jié)構(gòu),所以大多數(shù)的指令都不包含操作數(shù),只有一個(gè)操作碼。
局部變量表
局部變量表:Local Variables,被稱之為局部變量數(shù)組或本地變量表
定義為一個(gè)數(shù)字?jǐn)?shù)組,主要用于存儲(chǔ)方法參數(shù)和定義在方法體內(nèi)的局部變量,這些數(shù)據(jù)類型包括各類基本數(shù)據(jù)類型、對(duì)象引用(reference),以及returnAddress類型。
由于局部變量表是建立在線程的棧上,是線程的私有數(shù)據(jù),因此不存在數(shù)據(jù)安全問題。
局部變量表所需的容量大小是在編譯期確定下來的,并保存在方法的Code屬性的maximum local variables數(shù)據(jù)項(xiàng)中。在方法運(yùn)行期間是不會(huì)改變局部變量表的大小的。
操作數(shù)棧
操作數(shù)棧:Operand Stack ,使用數(shù)組實(shí)現(xiàn)的。
每一個(gè)獨(dú)立的棧幀除了包含局部變量表以外,還包含一個(gè)后進(jìn)先出(Last - In - First -Out)的 操作數(shù)棧,也可以稱之為 表達(dá)式棧(Expression Stack)
操作數(shù)棧,在方法執(zhí)行過程中,根據(jù)字節(jié)碼指令,往棧中寫入數(shù)據(jù)或提取數(shù)據(jù),即入棧(push)和 出棧(pop)
- 某些字節(jié)碼指令將值壓入操作數(shù)棧,其余的字節(jié)碼指令將操作數(shù)取出棧。使用它們后再把結(jié)果壓入棧
- 比如:執(zhí)行復(fù)制、交換、求和等操作
- 操作數(shù)棧,主要用于保存計(jì)算過程的中間結(jié)果,同時(shí)作為計(jì)算過程中變量臨時(shí)的存儲(chǔ)空間。
接下來就是本文的正式內(nèi)容,首先,我們先給出兩個(gè)結(jié)論:
- i++與++i在不同情況下可能會(huì)有不同的結(jié)論;
- 實(shí)例變量/類變量的i++并不是一個(gè)原子性的操作。
首先我們看一下i++與++i的解析:
當(dāng)i++或者++i沒有涉及到其他操作時(shí),兩者是沒有區(qū)別的。
// i++ public void method1(){ int i = 10; i++; } // ++i public void method2(){ int i = 10; ++i; }
對(duì)應(yīng)的字節(jié)碼指令操作為:
// method1 0 bipush 10 // 將10這個(gè)整數(shù)壓入操作數(shù)棧 2 istore_1 // 將操作數(shù)棧棧頂元素保存到局部變量表中索引為1處 3 iinc 1 by 1 // 局部變量表中索引為1處的元素,也就是i進(jìn)行自增(這一步是在局部變量表上直接進(jìn)行的,與操作數(shù)棧無關(guān)) 6 return // 方法返回 // method2 0 bipush 10 2 istore_1 3 iinc 1 by 1 // ++i 6 return
其中關(guān)于給出的具體字節(jié)碼細(xì)節(jié)以及棧幀中操作數(shù)棧、局部變量表在本文開頭給出了一些簡介,具體內(nèi)容不展開描述,讀者可翻閱與之有關(guān)的資料。
通過反編譯可以看出,i++與++i的字節(jié)碼在沒有和其他操作組合時(shí),字節(jié)碼是完全相同的。
當(dāng)i++或者++i涉及到其他操作時(shí),兩者的字節(jié)碼會(huì)有一些改變。
public void method7(){ int i = 10; int a = i++; int j = 20; int b = ++j; }
對(duì)應(yīng)的的字節(jié)碼指令:
0 bipush 10 2 istore_1 3 iload_1 // i++先從局部變量表中讀取i到操作數(shù)棧 4 iinc 1 by 1 // i直接在局部變量表上進(jìn)行自增 7 istore_2 // 將操作數(shù)棧上讀取到的i的值賦值給a,也就是10 8 bipush 20 10 istore_3 11 iinc 3 by 1 // ++j則先在局部變量表上進(jìn)行自增 14 iload_3 // 再從局部變量表中讀取j到操作數(shù)棧 15 istore 4 // 將操作數(shù)棧上讀取到的j的值賦值給b,也就是21 17 returns
通過反編譯可以看出,i++與++i的字節(jié)碼在沒有和其他操作組合時(shí),i++是先取值再自增,而++i是先自增再取值。
還有一個(gè)關(guān)于i=i++的解析:
public void method8(){ int i = 10; i = i++; System.out.println(i);//10 }
對(duì)應(yīng)的字節(jié)碼指令:
0 bipush 10 2 istore_1 3 iload_1 // 從局部變量表中讀取i到操作數(shù)棧 4 iinc 1 by 1 // i直接在局部變量表上進(jìn)行自增,此時(shí)i = 11 7 istore_1 // 將之前操作數(shù)棧上讀取到的i的值賦值給i,之前自增的值被覆蓋了,i = 10 8 getstatic #2 <java/lang/System.out> 11 iload_1 12 invokevirtual #5 <java/io/PrintStream.println> 15 return
然后看一下實(shí)例變量i++這行代碼的對(duì)應(yīng)的字節(jié)碼
首先我們定義一個(gè)類
/** * @author XiaoLe * @create 2020-12-04 21:15 * @description */ public class Test { private int i = 0; public void test(){ i++; } }
通過反編譯查看test方法中的字節(jié)碼:
0 aload_0 1 dup 2 getfield #2 <day001/Test.i> // 獲取到Test的i變量的值 5 iconst_1 // 將int類型常量1壓入棧 6 iadd // 棧頂兩個(gè)元素相加后返回值入棧 7 putfield #2 <day001/Test.i> 10 return
可以看到,i++這行代碼被拆分為三個(gè)字節(jié)碼,所以在一些并發(fā)情況下,i++如果不做同步處理,就可能會(huì)出現(xiàn)數(shù)據(jù)非一致性。
到此這篇關(guān)于Java中i++的一些問題總結(jié)的文章就介紹到這了,更多相關(guān)Java中i++問題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot集成MyBatis實(shí)現(xiàn)通用Mapper的配置及使用
關(guān)于MyBatis,大部分人都很熟悉。MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL、存儲(chǔ)過程以及高級(jí)映射。這篇文章主要介紹了Spring Boot集成MyBatis實(shí)現(xiàn)通用Mapper,需要的朋友可以參考下2018-08-08出現(xiàn)java.lang.NoSuchMethodException異常的解決(靠譜)
這篇文章主要介紹了出現(xiàn)java.lang.NoSuchMethodException異常的解決方案(靠譜),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Springboot yml如何獲取系統(tǒng)環(huán)境變量的值
這篇文章主要介紹了Springboot yml如何獲取系統(tǒng)環(huán)境變量的值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02如何使用Java redis實(shí)現(xiàn)發(fā)送手機(jī)驗(yàn)證碼功能
這篇文章主要介紹了如何使用Java redis實(shí)現(xiàn)發(fā)送手機(jī)驗(yàn)證碼功能,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05Java實(shí)現(xiàn)讀取鍵盤輸入保存到txt文件,再統(tǒng)計(jì)并輸出每個(gè)單詞出現(xiàn)次數(shù)的方法
這篇文章主要介紹了Java實(shí)現(xiàn)讀取鍵盤輸入保存到txt文件,再統(tǒng)計(jì)并輸出每個(gè)單詞出現(xiàn)次數(shù)的方法,涉及java文件I/O操作及字符串遍歷、運(yùn)算實(shí)現(xiàn)統(tǒng)計(jì)功能相關(guān)技巧,需要的朋友可以參考下2017-07-07