Java精通并发-透过字节码理解synchronized关键字
在上一次https://www.cnblogs.com/webor2006/p/11428408.html中对于synchronized关键字的作用做了一个实例详解,下面再来看一下这个程序:
请问下,如果一个线程访问了同一个对象的method1()方法之后,另外一个线程能否访问同一个对角的method4的静态方法呢?答案是肯定的,因为method1的锁是锁的对象,而method4锁的是MyClass的类对象,锁是不一样的,当然就可以并行的进行访问啦,
关于synchronized其实从使用角度来说是比较容易理解的,但是要想充分理解它的底层其实并不是那么简单的,“偏向锁”、“轻量级锁”、“重量级锁”、“自旋锁”,可能提到这些东东顺间就懵逼了,这些其实都是在底层所能隐射出来的知识点,所以接下来会多底层来审视这个synchronized,下面一点点来,先看一下新的例子:
对于这个代码其实在实际代码中是非常之常见的,那提个疑问:
其实,改成任何对象其效果都是一模一样的,比如改一下:
而为啥都用Object可能是成了一种标准了,所以实际代码中同步块中都会去锁一个Object对象,这里要注意:其实声明任何类型的对象其效果都是一模一样的,至于为啥在未来的底层探索中会来揭示的。
下面对这个简单的程序进行一下字节码的反编译来看一下synchronized在底层的表现:
xiongweideMacBook-Pro:main xiongwei$ javap -c com/javacurrency/test3/MyTest1.class Compiled from "MyTest1.java" public class com.javacurrency.test3.MyTest1 { public com.javacurrency.test3.MyTest1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: new #2 // class java/util/Date 8: dup 9: invokespecial #3 // Method java/util/Date."<init>":()V 12: putfield #4 // Field object:Ljava/util/Date; 15: return public void method(); Code: 0: aload_0 1: getfield #4 // Field object:Ljava/util/Date; 4: dup 5: astore_1 6: monitorenter 7: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 10: ldc #6 // String hello world 12: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 15: aload_1 16: monitorexit 17: goto 25 20: astore_2 21: aload_1 22: monitorexit 23: aload_2 24: athrow 25: return Exception table: from to target type 7 17 20 any 20 23 20 any }
其中如果想要看到更多的信息,比如常量池【这个在当时的JVM学习中已经详细学过了,就不过多解释了】,则可以用javap -v,如下:
xiongweideMacBook-Pro:main xiongwei$ javap -v com/javacurrency/test3/MyTest1.class Classfile /Users/xiongwei/Documents/workspace/IntelliJSpace/java_javacurrency/build/classes/java/main/com/javacurrency/test3/MyTest1.class Last modified 2019-8-30; size 719 bytes MD5 checksum 44fd4afb0a37765559faa8ec0abb158f Compiled from "MyTest1.java" public class com.javacurrency.test3.MyTest1 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #9.#26 // java/lang/Object."<init>":()V #2 = Class #27 // java/util/Date #3 = Methodref #2.#26 // java/util/Date."<init>":()V #4 = Fieldref #8.#28 // com/javacurrency/test3/MyTest1.object:Ljava/util/Date; #5 = Fieldref #29.#30 // java/lang/System.out:Ljava/io/PrintStream; #6 = String #31 // hello world #7 = Methodref #32.#33 // java/io/PrintStream.println:(Ljava/lang/String;)V #8 = Class #34 // com/javacurrency/test3/MyTest1 #9 = Class #35 // java/lang/Object #10 = Utf8 object #11 = Utf8 Ljava/util/Date; #12 = Utf8 <init> #13 = Utf8 ()V #14 = Utf8 Code #15 = Utf8 LineNumberTable #16 = Utf8 LocalVariableTable #17 = Utf8 this #18 = Utf8 Lcom/javacurrency/test3/MyTest1; #19 = Utf8 method #20 = Utf8 StackMapTable #21 = Class #34 // com/javacurrency/test3/MyTest1 #22 = Class #35 // java/lang/Object #23 = Class #36 // java/lang/Throwable #24 = Utf8 SourceFile #25 = Utf8 MyTest1.java #26 = NameAndType #12:#13 // "<init>":()V #27 = Utf8 java/util/Date #28 = NameAndType #10:#11 // object:Ljava/util/Date; #29 = Class #37 // java/lang/System #30 = NameAndType #38:#39 // out:Ljava/io/PrintStream; #31 = Utf8 hello world #32 = Class #40 // java/io/PrintStream #33 = NameAndType #41:#42 // println:(Ljava/lang/String;)V #34 = Utf8 com/javacurrency/test3/MyTest1 #35 = Utf8 java/lang/Object #36 = Utf8 java/lang/Throwable #37 = Utf8 java/lang/System #38 = Utf8 out #39 = Utf8 Ljava/io/PrintStream; #40 = Utf8 java/io/PrintStream #41 = Utf8 println #42 = Utf8 (Ljava/lang/String;)V { public com.javacurrency.test3.MyTest1(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: new #2 // class java/util/Date 8: dup 9: invokespecial #3 // Method java/util/Date."<init>":()V 12: putfield #4 // Field object:Ljava/util/Date; 15: return LineNumberTable: line 5: 0 line 6: 4 LocalVariableTable: Start Length Slot Name Signature 0 16 0 this Lcom/javacurrency/test3/MyTest1; public void method(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: getfield #4 // Field object:Ljava/util/Date; 4: dup 5: astore_1 6: monitorenter 7: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 10: ldc #6 // String hello world 12: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 15: aload_1 16: monitorexit 17: goto 25 20: astore_2 21: aload_1 22: monitorexit 23: aload_2 24: athrow 25: return Exception table: from to target type 7 17 20 any 20 23 20 any LineNumberTable: line 9: 0 line 10: 7 line 11: 15 line 12: 25 LocalVariableTable: Start Length Slot Name Signature 0 26 0 this Lcom/javacurrency/test3/MyTest1; StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 20 locals = [ class com/javacurrency/test3/MyTest1, class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4 } SourceFile: "MyTest1.java"
这里重点来看一下方法,首先是默认构造方法:
当然它不是我们要看的重点,要看的是method()方法:
关于上面的字节码的所有信息都在当时JVM的学习中学习过了,也不过多说,只看跟同步相关的东东:
因为要锁对象首先肯定得先获取对象才行,如下:
接着就执行同步块里面的方法了:
也就是:
当同步块执行完了,则会看到字节码中会退出锁:
但是!!!
下次再说~