首发2023-12-21 18:01·yuan人生
面试的时候经常有人问synchronized修饰方法和代码块底层实现有什么区别了,你来说下。实际做java开发很少有人关注这些东西,也基本没用。但面试就这样要能上天也能入地。那么我们从synchronized的字节码来看看两者到底有什么区别。
public class SynchCode {
public int num = 0;
public void test(){
synchronized (this){
num++;
}
}
}
编译后使用javap查看字节码:
PS F:\workspace2\testShell\target\classes\com\test> javap -v -c -s -l SynchCode.class
Classfile /F:/workspace2/testShell/target/classes/com/test/SynchCode.class
Last modified 2023-12-21; size 491 bytes
MD5 checksum 016ede9c00699bbca1c9f55b94e782b4
Compiled from "SynchCode.java"
public class com.test.SynchCode
minor version: 0
major version: 55
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#19 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#20 // com/test/SynchCode.num:I
#3 = Class #21 // com/test/SynchCode
#4 = Class #22 // java/lang/Object
#5 = Utf8 num
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/test/SynchCode;
#14 = Utf8 test
#15 = Utf8 StackMapTable
#16 = Class #23 // java/lang/Throwable
#17 = Utf8 SourceFile
#18 = Utf8 SynchCode.java
#19 = NameAndType #7:#8 // "<init>":()V
#20 = NameAndType #5:#6 // num:I
#21 = Utf8 com/test/SynchCode
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/Throwable
{
public int num;
descriptor: I
flags: ACC_PUBLIC
public com.test.SynchCode();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field num:I
9: return
LineNumberTable:
line 3: 0
line 5: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/test/SynchCode;
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: aload_0
5: dup
6: getfield #2 // Field num:I
9: iconst_1
10: iadd
11: putfield #2 // Field num:I
14: aload_1
15: monitorexit
16: goto 24
19: astore_2
20: aload_1
21: monitorexit
22: aload_2
23: athrow
24: return
Exception table:
from to target type
4 16 19 any
19 22 19 any
LineNumberTable:
line 8: 0
line 9: 4
line 10: 14
line 11: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 this Lcom/test/SynchCode;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 19
locals = [ class com/test/SynchCode, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
SourceFile: "SynchCode.java"
可以看出JVM通过进入、退出监视器(Monitor)来实现。这里为什么两个monitorexit,就是防止synchronized代码块出现异常,监视器也能正常退出,不至于死锁。
public class SynchMethod1 {
public int num = 0;
public synchronized void test(){
num++;
}
}
编译后使用javap查看字节码:
PS F:\workspace2\testShell\target\classes\com\test> javap -v -c -s -l SynchMethod1.class
Classfile /F:/workspace2/testShell/target/classes/com/test/SynchMethod1.class
Last modified 2023-12-21; size 394 bytes
MD5 checksum bdb278f9435449b792a5b02c368690ab
Compiled from "SynchMethod1.java"
public class com.test.SynchMethod1
minor version: 0
major version: 55
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#17 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#18 // com/test/SynchMethod1.num:I
#3 = Class #19 // com/test/SynchMethod1
#4 = Class #20 // java/lang/Object
#5 = Utf8 num
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/test/SynchMethod1;
#14 = Utf8 test
#15 = Utf8 SourceFile
#16 = Utf8 SynchMethod1.java
#17 = NameAndType #7:#8 // "<init>":()V
#18 = NameAndType #5:#6 // num:I
#19 = Utf8 com/test/SynchMethod1
#20 = Utf8 java/lang/Object
{
public int num;
descriptor: I
flags: ACC_PUBLIC
public com.test.SynchMethod1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field num:I
9: return
LineNumberTable:
line 3: 0
line 5: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/test/SynchMethod1;
public synchronized void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field num:I
5: iconst_1
6: iadd
7: putfield #2 // Field num:I
10: return
LineNumberTable:
line 8: 0
line 9: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/test/SynchMethod1;
}
SourceFile: "SynchMethod1.java"
可以看到相对synchronized代码块没有了monitorenter、monitorexit监视器进入和退出。只是在方法的flags中用ACC_SYNCHRONIZED来修饰代表是同步方法。
public class SynchMethod2 {
public static int num = 0;
public static synchronized void test(){
num++;
}
}
编译后使用javap查看字节码:
PS F:\workspace2\testShell\target\classes\com\test> javap -v -c -s -l SynchMethod2.class
Classfile /F:/workspace2/testShell/target/classes/com/test/SynchMethod2.class
Last modified 2023-12-21; size 419 bytes
MD5 checksum 85dbad02fdde2b42c5c8296a7544883d
Compiled from "SynchMethod2.java"
public class com.test.SynchMethod2
minor version: 0
major version: 55
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#19 // com/test/SynchMethod2.num:I
#3 = Class #20 // com/test/SynchMethod2
#4 = Class #21 // java/lang/Object
#5 = Utf8 num
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/test/SynchMethod2;
#14 = Utf8 test
#15 = Utf8 <clinit>
#16 = Utf8 SourceFile
#17 = Utf8 SynchMethod2.java
#18 = NameAndType #7:#8 // "<init>":()V
#19 = NameAndType #5:#6 // num:I
#20 = Utf8 com/test/SynchMethod2
#21 = Utf8 java/lang/Object
{
public static int num;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC
public com.test.SynchMethod2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/test/SynchMethod2;
public static synchronized void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field num:I
3: iconst_1
4: iadd
5: putstatic #2 // Field num:I
stack=1, locals=0, args_size=0
0: iconst_0
1: putstatic #2 // Field num:I
4: return
LineNumberTable:
line 5: 0
}
SourceFile: "SynchMethod2.java"
可以看到静态方法和非静态方法一样,只是在方法的flags中用ACC_SYNCHRONIZED来修饰代表是同步方法。
到这里,相信大家也该知道怎么回答该问题了。synchronized代码块底层字节码是用监视器monitorenter、monitorexit来实现的。synchronized修饰方法无论是静态方法还是非静态方法底层字节码都是在flags加了个ACC_SYNCHRONIZED来修饰代表是同步方法。