本文共 7384 字,大约阅读时间需要 24 分钟。
首先synchronized是阻塞式锁,悲观锁,会造成线程的挂起和恢复操作。JVM JDK1.2以后的线程模型是用户态和内核态共同存在,存在切换开销。
先看段代码
public class Test2 { public synchronized void test() { System.out.println("hello"); } public void test3() { synchronized (this) { System.out.println("hello"); } } public static synchronized void test2() { System.out.println("hello"); }}
常用用法,修饰对象,方法,静态方法
看原理
使用javap -c xx.class > 1.txt
public synchronized void test(); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String hello 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public void test3(); Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #3 // String hello 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_1 13: monitorexit 14: goto 22 17: astore_2 18: aload_1 19: monitorexit 20: aload_2 21: athrow 22: return Exception table: from to target type 4 14 17 any 17 20 17 any public static synchronized void test2(); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String hello 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return
通过javap -v即可显示详情
Classfile /Users/huahua/IdeaProjects/web-filter/target/classes/com/feng/demo/Test.class Last modified 2020-5-6; size 731 bytes MD5 checksum caa59f5b81fc01bdb2e60cd740a60c5a Compiled from "Test.java"public class com.feng.demo.Test minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Methodref #6.#23 // java/lang/Object."":()V #2 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #26 // hello #4 = Methodref #27.#28 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #29 // com/feng/demo/Test #6 = Class #30 // java/lang/Object #7 = Utf8 #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/feng/demo/Test; #14 = Utf8 test #15 = Utf8 test3 #16 = Utf8 StackMapTable #17 = Class #29 // com/feng/demo/Test #18 = Class #30 // java/lang/Object #19 = Class #31 // java/lang/Throwable #20 = Utf8 test2 #21 = Utf8 SourceFile #22 = Utf8 Test.java #23 = NameAndType #7:#8 // " ":()V #24 = Class #32 // java/lang/System #25 = NameAndType #33:#34 // out:Ljava/io/PrintStream; #26 = Utf8 hello #27 = Class #35 // java/io/PrintStream #28 = NameAndType #36:#37 // println:(Ljava/lang/String;)V #29 = Utf8 com/feng/demo/Test #30 = Utf8 java/lang/Object #31 = Utf8 java/lang/Throwable #32 = Utf8 java/lang/System #33 = Utf8 out #34 = Utf8 Ljava/io/PrintStream; #35 = Utf8 java/io/PrintStream #36 = Utf8 println #37 = Utf8 (Ljava/lang/String;)V{ public com.feng.demo.Test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object." ":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/feng/demo/Test; public synchronized void test(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String hello 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 5: 0 line 6: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Lcom/feng/demo/Test; public void test3(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #3 // String hello 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_1 13: monitorexit 14: goto 22 17: astore_2 18: aload_1 19: monitorexit 20: aload_2 21: athrow 22: return Exception table: from to target type 4 14 17 any 17 20 17 any LineNumberTable: line 9: 0 line 10: 4 line 11: 12 line 12: 22 LocalVariableTable: Start Length Slot Name Signature 0 23 0 this Lcom/feng/demo/Test; StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 17 locals = [ class com/feng/demo/Test, class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4 public static synchronized void test2(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED Code: stack=2, locals=0, args_size=0 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String hello 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 15: 0 line 16: 8}SourceFile: "Test.java"
对应上面的代码,class字节码,比如aload_1即class虚拟机字节码指令,其实很多语言可以编译为class文件,所有Java虚拟机实际上是class虚拟机。
可以看出本质monitorenter和monitorexit指令
同步方法其实是
flags: ACC_SYNCHRONIZED,通过-v参数可以直观地看见底层实现。
实际上,锁的本质满足如下:
1、原子性:当一个线程操作共享变量的时候,其他线程不能操作。实际就是串行修改。
2、可见性:当一个线程修改一个变量的时候,其他的线程及时知道更新缓存变量(参考计算机原理CPU与内存或主存的交互设计)
3、(可选)可重入:当一个线程拿到某个锁后,当该线程在释放该锁之前,可以重复获取该锁,当然释放次数与获取次数相同,否则死锁
从上可看出synchronized 的用途
1. 对于同步方法,锁是当前实例对象。
2. 对于同步方法块,锁是Synchonized括号里的对象。
3. 对于静态同步方法,锁是当前对象的Class对象。类似 synchronized (this.class)
JDK8及以后的版本JDK对synchronized做了优化,对于写操作频繁的线程安全推荐使用,性能较强
synchronized是操作对象头的monitor实现的,所以当时用sleep和wait的时候,在sleep方法中没有释放monitor,所以锁未释放,而wait方法释放了monitor对象,释放了锁
核心原理:JMM模型操作协议原子性
monitorenter和monitorexit指令操作lock和unlock指令, 保证主内存与工作内存的一致性