synchronized修饰方法和代码块底层实现有什么区别

发布时间:2023年12月21日

首发2023-12-21 18:01·yuan人生

面试的时候经常有人问synchronized修饰方法和代码块底层实现有什么区别了,你来说下。实际做java开发很少有人关注这些东西,也基本没用。但面试就这样要能上天也能入地。那么我们从synchronized的字节码来看看两者到底有什么区别。

  1. 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代码块出现异常,监视器也能正常退出,不至于死锁。

  1. 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来修饰代表是同步方法。

  1. 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来修饰代表是同步方法。

文章来源:https://blog.csdn.net/yztezhl/article/details/135137307
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。