synchronized 的实现原理

加不加 synchronized 有什么区别?

synchronized 作为悲观锁,锁住了什么?

synchronized 代码块怎么用

前面 3 篇文章讲了 synchronized 的同步方法和同步代码块两种用法,还有锁实例对象和锁 Class 对象两种锁机制。今天我们来看看同步方法和同步代码块的实现原理。

我们把前 3 篇有涉及到的 synchronized 方法全写在一起,如下面所示。

1public class SynchronizedPrincipleTest { 2 3 public void testNoSynchronized() { 4 System.out.println("hello testNoSynchronized"); 5 } 6 7 public synchronized void testSynchronizedMethod() { 8 System.out.println("hello testSynchronizedMethod"); 9 } 10 11 public static synchronized void testSynchronizedStatic() { 12 System.out.println("hello testSynchronizedStatic"); 13 } 14 15 public void testSynchronizedCodethis() { 16 synchronized (this) { 17 System.out.println("hello testSynchronizedCode"); 18 } 19 } 20 21 private Object lock = new Object(); 22 public void testSynchronizedCodeObject() { 23 synchronized (lock) { 24 System.out.println("hello testSynchronizedCodeObject"); 25 } 26 } 27 28 public void testSynchronizedCodeClass() { 29 synchronized (SynchronizedPrincipleTest.class) { 30 System.out.println("hello testSynchronizedCodeClass"); 31 } 32 } 33}

编写好代码之后,我们通过 javac 命令编译代码,使用 javap 命令反编译出汇编代码出来。命令如下所示。

1javac SynchronizedPrincipleTest.java 2javap -v SynchronizedCodeTest.class

得出我们要汇编代码。

1Classfile /D:/Workspace/finance/test/thread/src/main/java/com/liebrother/study/synchronizeds/SynchronizedPrincipleTest.class 2 Last modified Apr 26, 2020; size 1363 bytes 3 MD5 checksum a03ec0b152580bb465b1defe7965a60d 4 Compiled from "SynchronizedPrincipleTest.java" 5public class com.liebrother.study.synchronizeds.SynchronizedPrincipleTest 6 minor version: 0 7 major version: 52 8 flags: ACC_PUBLIC, ACC_SUPER 9Constant pool: 10 #1 = Methodref #2.#31 // java/lang/Object."<init>":()V 11 #2 = Class #32 // java/lang/Object 12 #3 = Fieldref #11.#33 // com/liebrother/study/synchronizeds/SynchronizedPrincipleTest.lock:Ljava/lang/Object; 13 #4 = Fieldref #34.#35 // java/lang/System.out:Ljava/io/PrintStream; 14 #5 = String #36 // hello testNoSynchronized 15 #6 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V 16 #7 = String #39 // hello testSynchronizedMethod 17 #8 = String #40 // hello testSynchronizedStatic 18 #9 = String #41 // hello testSynchronizedCode 19 #10 = String #42 // hello testSynchronizedCodeObject 20 #11 = Class #43 // com/liebrother/study/synchronizeds/SynchronizedPrincipleTest 21 #12 = String #44 // hello testSynchronizedCodeClass 22 #13 = Utf8 lock 23 #14 = Utf8 Ljava/lang/Object; 24 #15 = Utf8 <init> 25 #16 = Utf8 ()V 26 #17 = Utf8 Code 27 #18 = Utf8 LineNumberTable 28 #19 = Utf8 testNoSynchronized 29 #20 = Utf8 testSynchronizedMethod 30 #21 = Utf8 testSynchronizedStatic 31 #22 = Utf8 testSynchronizedCodethis 32 #23 = Utf8 StackMapTable 33 #24 = Class #43 // com/liebrother/study/synchronizeds/SynchronizedPrincipleTest 34 #25 = Class #32 // java/lang/Object 35 #26 = Class #45 // java/lang/Throwable 36 #27 = Utf8 testSynchronizedCodeObject 37 #28 = Utf8 testSynchronizedCodeClass 38 #29 = Utf8 SourceFile 39 #30 = Utf8 SynchronizedPrincipleTest.java 40 #31 = NameAndType #15:#16 // "<init>":()V 41 #32 = Utf8 java/lang/Object 42 #33 = NameAndType #13:#14 // lock:Ljava/lang/Object; 43 #34 = Class #46 // java/lang/System 44 #35 = NameAndType #47:#48 // out:Ljava/io/PrintStream; 45 #36 = Utf8 hello testNoSynchronized 46 #37 = Class #49 // java/io/PrintStream 47 #38 = NameAndType #50:#51 // println:(Ljava/lang/String;)V 48 #39 = Utf8 hello testSynchronizedMethod 49 #40 = Utf8 hello testSynchronizedStatic 50 #41 = Utf8 hello testSynchronizedCode 51 #42 = Utf8 hello testSynchronizedCodeObject 52 #43 = Utf8 com/liebrother/study/synchronizeds/SynchronizedPrincipleTest 53 #44 = Utf8 hello testSynchronizedCodeClass 54 #45 = Utf8 java/lang/Throwable 55 #46 = Utf8 java/lang/System 56 #47 = Utf8 out 57 #48 = Utf8 Ljava/io/PrintStream; 58 #49 = Utf8 java/io/PrintStream 59 #50 = Utf8 println 60 #51 = Utf8 (Ljava/lang/String;)V 61{ 62 public com.liebrother.study.synchronizeds.SynchronizedPrincipleTest(); 63 descriptor: ()V 64 flags: ACC_PUBLIC 65 Code: 66 stack=3, locals=1, args_size=1 67 0: aload_0 68 1: invokespecial #1 // Method java/lang/Object."<init>":()V 69 4: aload_0 70 5: new #2 // class java/lang/Object 71 8: dup 72 9: invokespecial #1 // Method java/lang/Object."<init>":()V 73 12: putfield #3 // Field lock:Ljava/lang/Object; 74 15: return 75 LineNumberTable: 76 line 7: 0 77 line 27: 4 78 79 /** 无 synchronized 修饰的代码 */ 80 public void testNoSynchronized(); 81 descriptor: ()V 82 flags: ACC_PUBLIC 83 Code: 84 stack=2, locals=1, args_size=1 85 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 86 3: ldc #5 // String hello testNoSynchronized 87 5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 88 8: return 89 LineNumberTable: 90 line 10: 0 91 line 11: 8 92 93 /** synchronized 修饰的实例方法 */ 94 public synchronized void testSynchronizedMethod(); 95 descriptor: ()V 96 flags: ACC_PUBLIC, ACC_SYNCHRONIZED /** 方法标识多了一个 ACC_SYNCHRONIZED */ 97 Code: 98 stack=2, locals=1, args_size=1 99 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 100 3: ldc #7 // String hello testSynchronizedMethod 101 5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 102 8: return 103 LineNumberTable: 104 line 14: 0 105 line 15: 8 106 107 /** synchronized 修饰的静态方法 */ 108 public static synchronized void testSynchronizedStatic(); 109 descriptor: ()V 110 flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED /** 方法标识多了 ACC_STATIC 和 ACC_SYNCHRONIZED */ 111 Code: 112 stack=2, locals=0, args_size=0 113 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 114 3: ldc #8 // String hello testSynchronizedStatic 115 5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 116 8: return 117 LineNumberTable: 118 line 18: 0 119 line 19: 8 120 121 /** synchronized 修饰的 this 代码块 */ 122 public void testSynchronizedCodethis(); 123 descriptor: ()V 124 flags: ACC_PUBLIC 125 Code: 126 stack=2, locals=3, args_size=1 127 0: aload_0 128 1: dup 129 2: astore_1 130 3: monitorenter /** 通过 monitorenter 命令进入监视器锁 */ 131 4: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 132 7: ldc #9 // String hello testSynchronizedCode 133 9: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 134 12: aload_1 135 13: monitorexit /** 通过 monitorexit 命令退出监视器锁 */ 136 14: goto 22 137 17: astore_2 138 18: aload_1 139 19: monitorexit /** 通过 monitorexit 命令退出监视器锁 */ 140 20: aload_2 141 21: athrow 142 22: return 143 Exception table: 144 from to target type 145 4 14 17 any 146 17 20 17 any 147 LineNumberTable: 148 line 22: 0 149 line 23: 4 150 line 24: 12 151 line 25: 22 152 StackMapTable: number_of_entries = 2 153 frame_type = 255 /* full_frame */ 154 offset_delta = 17 155 locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ] 156 stack = [ class java/lang/Throwable ] 157 frame_type = 250 /* chop */ 158 offset_delta = 4 159 160 /** synchronized 修饰的 object 代码块 */ 161 public void testSynchronizedCodeObject(); 162 descriptor: ()V 163 flags: ACC_PUBLIC 164 Code: 165 stack=2, locals=3, args_size=1 166 0: aload_0 167 1: getfield #3 // Field lock:Ljava/lang/Object; 168 4: dup 169 5: astore_1 170 6: monitorenter /** 通过 monitorenter 命令进入监视器锁 */ 171 7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 172 10: ldc #10 // String hello testSynchronizedCodeObject 173 12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 174 15: aload_1 175 16: monitorexit /** 通过 monitorexit 命令退出监视器锁 */ 176 17: goto 25 177 20: astore_2 178 21: aload_1 179 22: monitorexit /** 通过 monitorexit 命令退出监视器锁 */ 180 23: aload_2 181 24: athrow 182 25: return 183 Exception table: 184 from to target type 185 7 17 20 any 186 20 23 20 any 187 LineNumberTable: 188 line 29: 0 189 line 30: 7 190 line 31: 15 191 line 32: 25 192 StackMapTable: number_of_entries = 2 193 frame_type = 255 /* full_frame */ 194 offset_delta = 20 195 locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ] 196 stack = [ class java/lang/Throwable ] 197 frame_type = 250 /* chop */ 198 offset_delta = 4 199 200 /** synchronized 修饰的 xxx.Class 代码块 */ 201 public void testSynchronizedCodeClass(); 202 descriptor: ()V 203 flags: ACC_PUBLIC 204 Code: 205 stack=2, locals=3, args_size=1 206 0: ldc #11 // class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest 207 2: dup 208 3: astore_1 209 4: monitorenter /** 通过 monitorenter 命令进入监视器锁 */ 210 5: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 211 8: ldc #12 // String hello testSynchronizedCodeClass 212 10: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 213 13: aload_1 214 14: monitorexit /** 通过 monitorexit 命令退出监视器锁 */ 215 15: goto 23 216 18: astore_2 217 19: aload_1 218 20: monitorexit /** 通过 monitorexit 命令退出监视器锁 */ 219 21: aload_2 220 22: athrow 221 23: return 222 Exception table: 223 from to target type 224 5 15 18 any 225 18 21 18 any 226 LineNumberTable: 227 line 35: 0 228 line 36: 5 229 line 37: 13 230 line 38: 23 231 StackMapTable: number_of_entries = 2 232 frame_type = 255 /* full_frame */ 233 offset_delta = 18 234 locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ] 235 stack = [ class java/lang/Throwable ] 236 frame_type = 250 /* chop */ 237 offset_delta = 4 238} 239SourceFile: "SynchronizedPrincipleTest.java"

这段代码有点多,加了些注释方便大家看,这里我抽一些重要的点讲一下。

  1. 我们可以看到同步方法和同步代码块的同步实现不太一样。

同步方法的实现是在方法标识 flags 中加了 ACC_SYNCHRONIZED 标识,是一种隐式实现,具体是 JVM 在执行方法的时候,检查是否有 ACC_SYNCHRONIZED 同步标识,有的话会等待获取监控器 monitor,然后在方法执行结束时释放监控器 monitor。

同步代码块的实现是在加同步代码块前加上 monitorenter 指令,在同步代码块后加上 monitorexit 指令,每个对象都有一个 monitor 监视器,当 monitor 被某线程占用了,该线程就锁定了该 monitor。每个 monitor 都维护一个自己的计数器,当执行 monitorenter 时,该计数器 +1,当执行 monitorexit 时候释放锁,计数器变为 0。其他线程才可以尝试获得 monitor,对共享资源进行操作。

  1. 同步实例方法 testSynchronizedMethod() 和同步静态方法 testSynchronizedStatic() 差别只是在于 flags 有没有 ACC_STATIC 标识,其实锁实例对象还是锁 Class 对象,也是 JVM 底层实现根据这个标识去做判断,对我们来说是透明的。

  2. 同步代码块锁什么对象 this VS object VS xxx.class,在这个汇编代码可以看出来的。

this 的代码如下。在进入 monitor 监听器前,先获取 this 对象,也就是进入 this 对象的 monitor 锁。

1 0: aload_0 /** 加载当前 this 对象 */ 2 1: dup /** 将 this 对象压入栈顶 */ 3 2: astore_1 /** 从栈顶取出 this 对象 */ 4 3: monitorenter /** 获取 this 对象的 monitor 锁 */

object 的代码如下。在进入 monitor 监听器前,先获取 lock 对象,也就是进入 lock 对象的 monitor 锁。

10: aload_0 /** 加载当前 this 对象 */ 21: getfield #3 /** 获取 this 对象的实例变量 lock */ // Field lock:Ljava/lang/Object; 34: dup /** 将实例变量 lock 压入栈顶 */ 45: astore_1 /** 从栈顶取出 lock 对象 */ 56: monitorenter /** 获取 lock 对象的 monitor 锁 */

xxx.class 的代码如下。在进入 monitor 监听器前,先获取 Class 对象,也就是进入 Class 对象的 monitor 锁。

10: ldc #11 /** 从常量池中获取 SynchronizedPrincipleTest 类对象 */ // class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest 22: dup /** 将 Class 对象压入栈顶 */ 33: astore_1 /** 从栈顶取出 Class 对象 */ 44: monitorenter /** 获取 Class 对象的 monitor 锁 */

今天从 Java 的汇编代码来分析同步方法和同步代码块的底层实现,其实这块还不算是真正的底层实现,只是站在 Java 层面上来说,这已经是最底层了。站在 JVM 这是最高层,接下来会从 JVM 角度来分析为什么同步方法加上 ACC_SYNCHRONIZED 和 同步代码块加上 monitorenter & monitorexit 就可以实现多线程同步?

悄悄打个预防针,接下来的文章会有些晦涩难懂,但是我觉得很有必要弄懂它,弄懂了最底层原理,那么多线程就不怕了,弄懂了,后面会给大家讲的 AQS 就很容易懂,它是把 JVM 底层的实现搬到 Java 源库。

原创不易,大家多点个赞,非常感谢!

推荐阅读

synchronized 代码块怎么用

synchronized 作为悲观锁,锁住了什么?

加不加 synchronized 有什么区别?

从 JVM 视角看看 Java 守护线程

写了那么多年 Java 代码,终于 debug 到 JVM 了

全网最新最简单的 OpenJDK13 代码编译

了解Java线程优先级,更要知道对应操作系统的优先级,不然会踩坑

线程最最基础的知识

老板叫你别阻塞了

吃个快餐都能学到串行、并行、并发

泡一杯茶,学一学同异步

进程知多少?

设计模式看了又忘,忘了又看?

后台回复『设计模式』可以获取《一故事一设计模式》电子书

觉得文章有用帮忙转发&点赞,多谢朋友们!

LieBrother

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

synchronized 代码块怎么用

!(http://www.liebrother.com/upload/d84c8330b13b46588a58382158eeec87_image.png)加不加synchronized有什么区别?(https://my.oschina.net/liebrother/blog/4253687)synchronized作为悲观锁,锁住了

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )