Volatile不保證原子性
前言
通過前面對JMM的介紹,我們知道,各個執行緒對主記憶體中共享變數的操作都是各個執行緒各自拷貝到自己的工作記憶體進行操作後在寫回到主記憶體中的。
這就可能存在一個執行緒AAA修改了共享變數X的值,但是還未寫入主記憶體時,另外一個執行緒BBB又對主記憶體中同一共享變數X進行操作,但此時A執行緒工作記憶體中共享變數X對執行緒B來說是不可見,這種工作記憶體與主記憶體同步延遲現象就造成了可見性問題。
原子性
不可分割,完整性,也就是說某個執行緒正在做某個具體業務時,中間不可以被加塞或者被分割,需要具體完成,要麼同時成功,要麼同時失敗。
資料庫也經常提到事務具備原子性
程式碼測試
為了測試volatile是否保證原子性,我們建立了20個執行緒,然後每個執行緒分別迴圈1000次,來呼叫number++的方法
MyData myData = new MyData();
// 建立10個執行緒,執行緒裡面進行1000次迴圈
for (int i = 0; i < 20; i++) {
new Thread(() -> {
// 裡面
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
}
}, String.valueOf(i)).start();
}
最後通過 Thread.activeCount(),來感知20個執行緒是否執行完畢,這裡判斷執行緒數是否大於2,為什麼是2?因為預設是有兩個執行緒的,一個main執行緒,一個gc執行緒
// 需要等待上面20個執行緒都計算完成後,在用main執行緒取得最終的結果值
while(Thread.activeCount() > 2) {
// yield表示不執行
Thread.yield();
}
然後線上程執行完畢後,我們在檢視number的值,假設volatile保證原子性的話,那麼最後輸出的值應該是
20 * 1000 = 20000,
完整程式碼如下所示:
/**
* Volatile Java虛擬機器提供的輕量級同步機制
*
* 可見性(及時通知)
* 不保證原子性
* 禁止指令重排
*
* @author: 陌溪
* @create: 2020-03-09-15:58
*/
import java.util.concurrent.TimeUnit;
/**
* 假設是主實體記憶體
*/
class MyData {
/**
* volatile 修飾的關鍵字,是為了增加 主執行緒和執行緒之間的可見性,只要有一個執行緒修改了記憶體中的值,其它執行緒也能馬上感知
*/
volatile int number = 0;
public void addTo60() {
this.number = 60;
}
/**
* 注意,此時number 前面是加了volatile修飾
*/
public void addPlusPlus() {
number ++;
}
}
/**
* 驗證volatile的可見性
* 1、 假設int number = 0, number變數之前沒有新增volatile關鍵字修飾
* 2、新增了volatile,可以解決可見性問題
*
* 驗證volatile不保證原子性
* 1、原子性指的是什麼意思?
*/
public class VolatileDemo {
public static void main(String args []) {
MyData myData = new MyData();
// 建立10個執行緒,執行緒裡面進行1000次迴圈
for (int i = 0; i < 20; i++) {
new Thread(() -> {
// 裡面
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
}
}, String.valueOf(i)).start();
}
// 需要等待上面20個執行緒都計算完成後,在用main執行緒取得最終的結果值
// 這裡判斷執行緒數是否大於2,為什麼是2?因為預設是有兩個執行緒的,一個main執行緒,一個gc執行緒
while(Thread.activeCount() > 2) {
// yield表示不執行
Thread.yield();
}
// 檢視最終的值
// 假設volatile保證原子性,那麼輸出的值應該為: 20 * 1000 = 20000
System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);
}
}
最終結果我們會發現,number輸出的值並沒有20000,而且是每次執行的結果都不一致的,這說明了volatile修飾的變數不保證原子性
第一次:
第二次:
第三次:
為什麼出現數值丟失
各自執行緒在寫入主記憶體的時候,出現了資料的丟失,而引起的數值缺失的問題
下面我們將一個簡單的number++操作,轉換為位元組碼檔案一探究竟
public class T1 {
volatile int n = 0;
public void add() {
n++;
}
}
轉換後的位元組碼檔案
public class com.moxi.interview.study.thread.T1 {
volatile int n;
public com.moxi.interview.study.thread.T1();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field n:I
9: return
public void add();
Code:
0: aload_0
1: dup
2: getfield #2 // Field n:I
5: iconst_1
6: iadd
7: putfield #2 // Field n:I
10: return
}
這裡檢視位元組碼的操作,是用到了IDEA的javap命令
我們首先,使用IDEA提供的External Tools,來擴充套件javap命令
完成上述操作後,我們在需要檢視位元組碼的檔案下,右鍵選擇 External Tools即可
如果出現了找不到指定類,那是因為我們建立的是spring boot的maven專案,我們之前需要執行mvn package命令,進行打包操作,將其編譯成class檔案
移動到底部,有一份位元組碼指令對照表,方便我們進行閱讀
下面我們就針對 add() 這個方法的位元組碼檔案進行分析
public void add();
Code:
0: aload_0
1: dup
2: getfield #2 // Field n:I
5: iconst_1
6: iadd
7: putfield #2 // Field n:I
10: return
我們能夠發現 n++這條命令,被拆分成了3個指令
- 執行
getfield
從主記憶體拿到原始n - 執行
iadd
進行加1操作 - 執行
putfileld
把累加後的值寫回主記憶體
假設我們沒有加 synchronized
那麼第一步就可能存在著,三個執行緒同時通過getfield命令,拿到主存中的 n值,然後三個執行緒,各自在自己的工作記憶體中進行加1操作,但他們併發進行 iadd
命令的時候,因為只能一個進行寫,所以其它操作會被掛起,假設1執行緒,先進行了寫操作,在寫完後,volatile的可見性,應該需要告訴其它兩個執行緒,主記憶體的值已經被修改了,但是因為太快了,其它兩個執行緒,陸續執行 iadd
命令,進行寫入操作,這就造成了其他執行緒沒有接受到主記憶體n的改變,從而覆蓋了原來的值,出現寫丟失,這樣也就讓最終的結果少於20000
如何解決
因此這也說明,在多執行緒環境下 number ++ 在多執行緒環境下是非執行緒安全的,解決的方法有哪些呢?
- 在方法上加入 synchronized
public synchronized void addPlusPlus() {
number ++;
}
執行結果:
我們能夠發現引入synchronized關鍵字後,保證了該方法每次只能夠一個執行緒進行訪問和操作,最終輸出的結果也就為20000
其它解決方法
上面的方法引入synchronized,雖然能夠保證原子性,但是為了解決number++,而引入重量級的同步機制,有種 殺雞焉用牛刀
除了引用synchronized關鍵字外,還可以使用JUC下面的原子包裝類,即剛剛的int型別的number,可以使用AtomicInteger來代替
/**
* 建立一個原子Integer包裝類,預設為0
*/
AtomicInteger atomicInteger = new AtomicInteger();
public void addAtomic() {
// 相當於 atomicInter ++
atomicInteger.getAndIncrement();
}
然後同理,繼續剛剛的操作
// 建立10個執行緒,執行緒裡面進行1000次迴圈
for (int i = 0; i < 20; i++) {
new Thread(() -> {
// 裡面
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
myData.addAtomic();
}
}, String.valueOf(i)).start();
}
最後輸出
// 假設volatile保證原子性,那麼輸出的值應該為: 20 * 1000 = 20000
System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);
System.out.println(Thread.currentThread().getName() + "\t finally atomicNumber value: " + myData.atomicInteger);
下面的結果,一個是引入synchronized,一個是使用了原子包裝類AtomicInteger
位元組碼指令表
為了方便閱讀JVM位元組碼檔案,我從網上找了一份位元組碼指令表
位元組碼 | 助記符 | 指令含義 |
---|---|---|
0x00 | nop | None |
0x01 | aconst_null | 將null推送至棧頂 |
0x02 | iconst_m1 | 將int型-1推送至棧頂 |
0x03 | iconst_0 | 將int型0推送至棧頂 |
0x04 | iconst_1 | 將int型1推送至棧頂 |
0x05 | iconst_2 | 將int型2推送至棧頂 |
0x06 | iconst_3 | 將int型3推送至棧頂 |
0x07 | iconst_4 | 將int型4推送至棧頂 |
0x08 | iconst_5 | 將int型5推送至棧頂 |
0x09 | lconst_0 | 將long型0推送至棧頂 |
0x0a | lconst_1 | 將long型1推送至棧頂 |
0x0b | fconst_0 | 將float型0推送至棧頂 |
0x0c | fconst_1 | 將float型1推送至棧頂 |
0x0d | fconst_2 | 將float型2推送至棧頂 |
0x0e | dconst_0 | 將double型0推送至棧頂 |
0x0f | dconst_1 | 將double型1推送至棧頂 |
0x10 | bipush | 將單位元組的常量值(-128~127)推送至棧頂 |
0x11 | sipush | 將一個短整型常量(-32768~32767)推送至棧頂 |
0x12 | ldc | 將int,float或String型常量值從常量池中推送至棧頂 |
0x13 | ldc_w | 將int,float或String型常量值從常量池中推送至棧頂(寬索引) |
0x14 | ldc2_w | 將long或double型常量值從常量池中推送至棧頂(寬索引) |
0x15 | iload | 將指定的int型本地變數推送至棧頂 |
0x16 | lload | 將指定的long型本地變數推送至棧頂 |
0x17 | fload | 將指定的float型本地變數推送至棧頂 |
0x18 | dload | 將指定的double型本地變數推送至棧頂 |
0x19 | aload | 將指定的引用型別本地變數推送至棧頂 |
0x1a | iload_0 | 將第一個int型本地變數推送至棧頂 |
0x1b | iload_1 | 將第二個int型本地變數推送至棧頂 |
0x1c | iload_2 | 將第三個int型本地變數推送至棧頂 |
0x1d | iload_3 | 將第四個int型本地變數推送至棧頂 |
0x1e | lload_0 | 將第一個long型本地變數推送至棧頂 |
0x1f | lload_1 | 將第二個long型本地變數推送至棧頂 |
0x20 | lload_2 | 將第三個long型本地變數推送至棧頂 |
0x21 | lload_3 | 將第四個long型本地變數推送至棧頂 |
0x22 | fload_0 | 將第一個float型本地變數推送至棧頂 |
0x23 | fload_1 | 將第二個float型本地變數推送至棧頂 |
0x24 | fload_2 | 將第三個float型本地變數推送至棧頂 |
0x25 | fload_3 | 將第四個float型本地變數推送至棧頂 |
0x26 | dload_0 | 將第一個double型本地變數推送至棧頂 |
0x27 | dload_1 | 將第二個double型本地變數推送至棧頂 |
0x28 | dload_2 | 將第三個double型本地變數推送至棧頂 |
0x29 | dload_3 | 將第四個double型本地變數推送至棧頂 |
0x2a | aload_0 | 將第一個引用型別本地變數推送至棧頂 |
0x2b | aload_1 | 將第二個引用型別本地變數推送至棧頂 |
0x2c | aload_2 | 將第三個引用型別本地變數推送至棧頂 |
0x2d | aload_3 | 將第四個引用型別本地變數推送至棧頂 |
0x2e | iaload | 將int型陣列指定索引的值推送至棧頂 |
0x2f | laload | 將long型陣列指定索引的值推送至棧頂 |
0x30 | faload | 將float型陣列指定索引的值推送至棧頂 |
0x31 | daload | 將double型陣列指定索引的值推送至棧頂 |
0x32 | aaload | 將引用型別陣列指定索引的值推送至棧頂 |
0x33 | baload | 將boolean或byte型陣列指定索引的值推送至棧頂 |
0x34 | caload | 將char型陣列指定索引的值推送至棧頂 |
0x35 | saload | 將short型陣列指定索引的值推送至棧頂 |
0x36 | istore | 將棧頂int型數值存入指定本地變數 |
0x37 | lstore | 將棧頂long型數值存入指定本地變數 |
0x38 | fstore | 將棧頂float型數值存入指定本地變數 |
0x39 | dstore | 將棧頂double型數值存入指定本地變數 |
0x3a | astore | 將棧頂引用型別數值存入指定本地變數 |
0x3b | istore_0 | 將棧頂int型數值存入第一個本地變數 |
0x3c | istore_1 | 將棧頂int型數值存入第二個本地變數 |
0x3d | istore_2 | 將棧頂int型數值存入第三個本地變數 |
0x3e | istore_3 | 將棧頂int型數值存入第四個本地變數 |
0x3f | lstore_0 | 將棧頂long型數值存入第一個本地變數 |
0x40 | lstore_1 | 將棧頂long型數值存入第二個本地變數 |
0x41 | lstore_2 | 將棧頂long型數值存入第三個本地變數 |
0x42 | lstore_3 | 將棧頂long型數值存入第四個本地變數 |
0x43 | fstore_0 | 將棧頂float型數值存入第一個本地變數 |
0x44 | fstore_1 | 將棧頂float型數值存入第二個本地變數 |
0x45 | fstore_2 | 將棧頂float型數值存入第三個本地變數 |
0x46 | fstore_3 | 將棧頂float型數值存入第四個本地變數 |
0x47 | dstore_0 | 將棧頂double型數值存入第一個本地變數 |
0x48 | dstore_1 | 將棧頂double型數值存入第二個本地變數 |
0x49 | dstore_2 | 將棧頂double型數值存入第三個本地變數 |
0x4a | dstore_3 | 將棧頂double型數值存入第四個本地變數 |
0x4b | astore_0 | 將棧頂引用型數值存入第一個本地變數 |
0x4c | astore_1 | 將棧頂引用型數值存入第二個本地變數 |
0x4d | astore_2 | 將棧頂引用型數值存入第三個本地變數 |
0x4e | astore_3 | 將棧頂引用型數值存入第四個本地變數 |
0x4f | iastore | 將棧頂int型數值存入指定陣列的指定索引位置 |
0x50 | lastore | 將棧頂long型數值存入指定陣列的指定索引位置 |
0x51 | fastore | 將棧頂float型數值存入指定陣列的指定索引位置 |
0x52 | dastore | 將棧頂double型數值存入指定陣列的指定索引位置 |
0x53 | aastore | 將棧頂引用型數值存入指定陣列的指定索引位置 |
0x54 | bastore | 將棧頂boolean或byte型數值存入指定陣列的指定索引位置 |
0x55 | castore | 將棧頂char型數值存入指定陣列的指定索引位置 |
0x56 | sastore | 將棧頂short型數值存入指定陣列的指定索引位置 |
0x57 | pop | 將棧頂數值彈出(數值不能是long或double型別的) |
0x58 | pop2 | 將棧頂的一個(對於非long或double型別)或兩個數值(對於非long或double的其他型別)彈出 |
0x59 | dup | 複製棧頂數值並將複製值壓入棧頂 |
0x5a | dup_x1 | 複製棧頂數值並將兩個複製值壓入棧頂 |
0x5b | dup_x2 | 複製棧頂數值並將三個(或兩個)複製值壓入棧頂 |
0x5c | dup2 | 複製棧頂一個(對於long或double型別)或兩個(對於非long或double的其他型別)數值並將複製值壓入棧頂 |
0x5d | dup2_x1 | dup_x1指令的雙倍版本 |
0x5e | dup2_x2 | dup_x2指令的雙倍版本 |
0x5f | swap | 將棧頂最頂端的兩個數值互換(數值不能是long或double型別) |
0x60 | iadd | 將棧頂兩int型數值相加並將結果壓入棧頂 |
0x61 | ladd | 將棧頂兩long型數值相加並將結果壓入棧頂 |
0x62 | fadd | 將棧頂兩float型數值相加並將結果壓入棧頂 |
0x63 | dadd | 將棧頂兩double型數值相加並將結果壓入棧頂 |
0x64 | isub | 將棧頂兩int型數值相減並將結果壓入棧頂 |
0x65 | lsub | 將棧頂兩long型數值相減並將結果壓入棧頂 |
0x66 | fsub | 將棧頂兩float型數值相減並將結果壓入棧頂 |
0x67 | dsub | 將棧頂兩double型數值相減並將結果壓入棧頂 |
0x68 | imul | 將棧頂兩int型數值相乘並將結果壓入棧頂 |
0x69 | lmul | 將棧頂兩long型數值相乘並將結果壓入棧頂 |
0x6a | fmul | 將棧頂兩float型數值相乘並將結果壓入棧頂 |
0x6b | dmul | 將棧頂兩double型數值相乘並將結果壓入棧頂 |
0x6c | idiv | 將棧頂兩int型數值相除並將結果壓入棧頂 |
0x6d | ldiv | 將棧頂兩long型數值相除並將結果壓入棧頂 |
0x6e | fdiv | 將棧頂兩float型數值相除並將結果壓入棧頂 |
0x6f | ddiv | 將棧頂兩double型數值相除並將結果壓入棧頂 |
0x70 | irem | 將棧頂兩int型數值作取模運算並將結果壓入棧頂 |
0x71 | lrem | 將棧頂兩long型數值作取模運算並將結果壓入棧頂 |
0x72 | frem | 將棧頂兩float型數值作取模運算並將結果壓入棧頂 |
0x73 | drem | 將棧頂兩double型數值作取模運算並將結果壓入棧頂 |
0x74 | ineg | 將棧頂int型數值取負並將結果壓入棧頂 |
0x75 | lneg | 將棧頂long型數值取負並將結果壓入棧頂 |
0x76 | fneg | 將棧頂float型數值取負並將結果壓入棧頂 |
0x77 | dneg | 將棧頂double型數值取負並將結果壓入棧頂 |
0x78 | ishl | 將int型數值左移指定位數並將結果壓入棧頂 |
0x79 | lshl | 將long型數值左移指定位數並將結果壓入棧頂 |
0x7a | ishr | 將int型數值右(帶符號)移指定位數並將結果壓入棧頂 |
0x7b | lshr | 將long型數值右(帶符號)移指定位數並將結果壓入棧頂 |
0x7c | iushr | 將int型數值右(無符號)移指定位數並將結果壓入棧頂 |
0x7d | lushr | 將long型數值右(無符號)移指定位數並將結果壓入棧頂 |
0x7e | iand | 將棧頂兩int型數值"按位與"並將結果壓入棧頂 |
0x7f | land | 將棧頂兩long型數值"按位與"並將結果壓入棧頂 |
0x80 | ior | 將棧頂兩int型數值"按位或"並將結果壓入棧頂 |
0x81 | lor | 將棧頂兩long型數值"按位或"並將結果壓入棧頂 |
0x82 | ixor | 將棧頂兩int型數值"按位異或"並將結果壓入棧頂 |
0x83 | lxor | 將棧頂兩long型數值"按位異或"並將結果壓入棧頂 |
0x84 | iinc | 將指定int型變數增加指定值(如i++, i--, i+=2等) |
0x85 | i2l | 將棧頂int型數值強制轉換為long型數值並將結果壓入棧頂 |
0x86 | i2f | 將棧頂int型數值強制轉換為float型數值並將結果壓入棧頂 |
0x87 | i2d | 將棧頂int型數值強制轉換為double型數值並將結果壓入棧頂 |
0x88 | l2i | 將棧頂long型數值強制轉換為int型數值並將結果壓入棧頂 |
0x89 | l2f | 將棧頂long型數值強制轉換為float型數值並將結果壓入棧頂 |
0x8a | l2d | 將棧頂long型數值強制轉換為double型數值並將結果壓入棧頂 |
0x8b | f2i | 將棧頂float型數值強制轉換為int型數值並將結果壓入棧頂 |
0x8c | f2l | 將棧頂float型數值強制轉換為long型數值並將結果壓入棧頂 |
0x8d | f2d | 將棧頂float型數值強制轉換為double型數值並將結果壓入棧頂 |
0x8e | d2i | 將棧頂double型數值強制轉換為int型數值並將結果壓入棧頂 |
0x8f | d2l | 將棧頂double型數值強制轉換為long型數值並將結果壓入棧頂 |
0x90 | d2f | 將棧頂double型數值強制轉換為float型數值並將結果壓入棧頂 |
0x91 | i2b | 將棧頂int型數值強制轉換為byte型數值並將結果壓入棧頂 |
0x92 | i2c | 將棧頂int型數值強制轉換為char型數值並將結果壓入棧頂 |
0x93 | i2s | 將棧頂int型數值強制轉換為short型數值並將結果壓入棧頂 |
0x94 | lcmp | 比較棧頂兩long型數值大小, 並將結果(1, 0或-1)壓入棧頂 |
0x95 | fcmpl | 比較棧頂兩float型數值大小, 並將結果(1, 0或-1)壓入棧頂; 當其中一個數值為NaN 時, 將-1壓入棧頂 |
0x96 | fcmpg | 比較棧頂兩float型數值大小, 並將結果(1, 0或-1)壓入棧頂; 當其中一個數值為NaN 時, 將1壓入棧頂 |
0x97 | dcmpl | 比較棧頂兩double型數值大小, 並將結果(1, 0或-1)壓入棧頂; 當其中一個數值為NaN 時, 將-1壓入棧頂 |
0x98 | dcmpg | 比較棧頂兩double型數值大小, 並將結果(1, 0或-1)壓入棧頂; 當其中一個數值為NaN 時, 將1壓入棧頂 |
0x99 | ifeq | 當棧頂int型數值等於0時跳轉 |
0x9a | ifne | 當棧頂int型數值不等於0時跳轉 |
0x9b | iflt | 當棧頂int型數值小於0時跳轉 |
0x9c | ifge | 當棧頂int型數值大於等於0時跳轉 |
0x9d | ifgt | 當棧頂int型數值大於0時跳轉 |
0x9e | ifle | 當棧頂int型數值小於等於0時跳轉 |
0x9f | if_icmpeq | 比較棧頂兩int型數值大小, 當結果等於0時跳轉 |
0xa0 | if_icmpne | 比較棧頂兩int型數值大小, 當結果不等於0時跳轉 |
0xa1 | if_icmplt | 比較棧頂兩int型數值大小, 當結果小於0時跳轉 |
0xa2 | if_icmpge | 比較棧頂兩int型數值大小, 當結果大於等於0時跳轉 |
0xa3 | if_icmpgt | 比較棧頂兩int型數值大小, 當結果大於0時跳轉 |
0xa4 | if_icmple | 比較棧頂兩int型數值大小, 當結果小於等於0時跳轉 |
0xa5 | if_acmpeq | 比較棧頂兩引用型數值, 當結果相等時跳轉 |
0xa6 | if_acmpne | 比較棧頂兩引用型數值, 當結果不相等時跳轉 |
0xa7 | goto | 無條件跳轉 |
0xa8 | jsr | 跳轉至指定的16位offset位置, 並將jsr的下一條指令地址壓入棧頂 |
0xa9 | ret | 返回至本地變數指定的index的指令位置(一般與jsr或jsr_w聯合使用) |
0xaa | tableswitch | 用於switch條件跳轉, case值連續(可變長度指令) |
0xab | lookupswitch | 用於switch條件跳轉, case值不連續(可變長度指令) |
0xac | ireturn | 從當前方法返回int |
0xad | lreturn | 從當前方法返回long |
0xae | freturn | 從當前方法返回float |
0xaf | dreturn | 從當前方法返回double |
0xb0 | areturn | 從當前方法返回物件引用 |
0xb1 | return | 從當前方法返回void |
0xb2 | getstatic | 獲取指定類的靜態域, 並將其壓入棧頂 |
0xb3 | putstatic | 為指定類的靜態域賦值 |
0xb4 | getfield | 獲取指定類的例項域, 並將其壓入棧頂 |
0xb5 | putfield | 為指定類的例項域賦值 |
0xb6 | invokevirtual | 呼叫例項方法 |
0xb7 | invokespecial | 呼叫超類構建方法, 例項初始化方法, 私有方法 |
0xb8 | invokestatic | 呼叫靜態方法 |
0xb9 | invokeinterface | 呼叫介面方法 |
0xba | invokedynamic | 呼叫動態方法 |
0xbb | new | 建立一個物件, 並將其引用引用值壓入棧頂 |
0xbc | newarray | 建立一個指定的原始型別(如int, float, char等)的陣列, 並將其引用值壓入棧頂 |
0xbd | anewarray | 建立一個引用型(如類, 介面, 陣列)的陣列, 並將其引用值壓入棧頂 |
0xbe | arraylength | 獲取陣列的長度值並壓入棧頂 |
0xbf | athrow | 將棧頂的異常丟擲 |
0xc0 | checkcast | 檢驗型別轉換, 檢驗未通過將丟擲 ClassCastException |
0xc1 | instanceof | 檢驗物件是否是指定類的實際, 如果是將1壓入棧頂, 否則將0壓入棧頂 |
0xc2 | monitorenter | 獲得物件的鎖, 用於同步方法或同步塊 |
0xc3 | monitorexit | 釋放物件的鎖, 用於同步方法或同步塊 |
0xc4 | wide | 擴充套件本地變數的寬度 |
0xc5 | multianewarray | 建立指定型別和指定維度的多維陣列(執行該指令時, 操作棧中必須包含各維度的長度值), 並將其引用壓入棧頂 |
0xc6 | ifnull | 為null時跳轉 |
0xc7 | ifnonnull | 不為null時跳轉 |
0xc8 | goto_w | 無條件跳轉(寬索引) |
0xc9 | jsr_w | 跳轉至指定的32位offset位置, 並將jsr_w的下一條指令地址壓入棧頂 |