加不加 synchronized 有什么区别?

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

synchronized 代码块怎么用

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

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

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

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

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

得出我们要汇编代码。

  1. Classfile /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"
  5. public class com.liebrother.study.synchronizeds.SynchronizedPrincipleTest
  6. minor version: 0
  7. major version: 52
  8. flags: ACC_PUBLIC, ACC_SUPER
  9. Constant 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. /** 无 synchronized 修饰的代码 */
  79. public void testNoSynchronized();
  80. descriptor: ()V
  81. flags: ACC_PUBLIC
  82. Code:
  83. stack=2, locals=1, args_size=1
  84. 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
  85. 3: ldc #5 // String hello testNoSynchronized
  86. 5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  87. 8: return
  88. LineNumberTable:
  89. line 10: 0
  90. line 11: 8
  91. /** synchronized 修饰的实例方法 */
  92. public synchronized void testSynchronizedMethod();
  93. descriptor: ()V
  94. flags: ACC_PUBLIC, ACC_SYNCHRONIZED /** 方法标识多了一个 ACC_SYNCHRONIZED */
  95. Code:
  96. stack=2, locals=1, args_size=1
  97. 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
  98. 3: ldc #7 // String hello testSynchronizedMethod
  99. 5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  100. 8: return
  101. LineNumberTable:
  102. line 14: 0
  103. line 15: 8
  104. /** synchronized 修饰的静态方法 */
  105. public static synchronized void testSynchronizedStatic();
  106. descriptor: ()V
  107. flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED /** 方法标识多了 ACC_STATIC 和 ACC_SYNCHRONIZED */
  108. Code:
  109. stack=2, locals=0, args_size=0
  110. 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
  111. 3: ldc #8 // String hello testSynchronizedStatic
  112. 5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  113. 8: return
  114. LineNumberTable:
  115. line 18: 0
  116. line 19: 8
  117. /** synchronized 修饰的 this 代码块 */
  118. public void testSynchronizedCodethis();
  119. descriptor: ()V
  120. flags: ACC_PUBLIC
  121. Code:
  122. stack=2, locals=3, args_size=1
  123. 0: aload_0
  124. 1: dup
  125. 2: astore_1
  126. 3: monitorenter /** 通过 monitorenter 命令进入监视器锁 */
  127. 4: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
  128. 7: ldc #9 // String hello testSynchronizedCode
  129. 9: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  130. 12: aload_1
  131. 13: monitorexit /** 通过 monitorexit 命令退出监视器锁 */
  132. 14: goto 22
  133. 17: astore_2
  134. 18: aload_1
  135. 19: monitorexit /** 通过 monitorexit 命令退出监视器锁 */
  136. 20: aload_2
  137. 21: athrow
  138. 22: return
  139. Exception table:
  140. from to target type
  141. 4 14 17 any
  142. 17 20 17 any
  143. LineNumberTable:
  144. line 22: 0
  145. line 23: 4
  146. line 24: 12
  147. line 25: 22
  148. StackMapTable: number_of_entries = 2
  149. frame_type = 255 /* full_frame */
  150. offset_delta = 17
  151. locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ]
  152. stack = [ class java/lang/Throwable ]
  153. frame_type = 250 /* chop */
  154. offset_delta = 4
  155. /** synchronized 修饰的 object 代码块 */
  156. public void testSynchronizedCodeObject();
  157. descriptor: ()V
  158. flags: ACC_PUBLIC
  159. Code:
  160. stack=2, locals=3, args_size=1
  161. 0: aload_0
  162. 1: getfield #3 // Field lock:Ljava/lang/Object;
  163. 4: dup
  164. 5: astore_1
  165. 6: monitorenter /** 通过 monitorenter 命令进入监视器锁 */
  166. 7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
  167. 10: ldc #10 // String hello testSynchronizedCodeObject
  168. 12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  169. 15: aload_1
  170. 16: monitorexit /** 通过 monitorexit 命令退出监视器锁 */
  171. 17: goto 25
  172. 20: astore_2
  173. 21: aload_1
  174. 22: monitorexit /** 通过 monitorexit 命令退出监视器锁 */
  175. 23: aload_2
  176. 24: athrow
  177. 25: return
  178. Exception table:
  179. from to target type
  180. 7 17 20 any
  181. 20 23 20 any
  182. LineNumberTable:
  183. line 29: 0
  184. line 30: 7
  185. line 31: 15
  186. line 32: 25
  187. StackMapTable: number_of_entries = 2
  188. frame_type = 255 /* full_frame */
  189. offset_delta = 20
  190. locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ]
  191. stack = [ class java/lang/Throwable ]
  192. frame_type = 250 /* chop */
  193. offset_delta = 4
  194. /** synchronized 修饰的 xxx.Class 代码块 */
  195. public void testSynchronizedCodeClass();
  196. descriptor: ()V
  197. flags: ACC_PUBLIC
  198. Code:
  199. stack=2, locals=3, args_size=1
  200. 0: ldc #11 // class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
  201. 2: dup
  202. 3: astore_1
  203. 4: monitorenter /** 通过 monitorenter 命令进入监视器锁 */
  204. 5: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
  205. 8: ldc #12 // String hello testSynchronizedCodeClass
  206. 10: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  207. 13: aload_1
  208. 14: monitorexit /** 通过 monitorexit 命令退出监视器锁 */
  209. 15: goto 23
  210. 18: astore_2
  211. 19: aload_1
  212. 20: monitorexit /** 通过 monitorexit 命令退出监视器锁 */
  213. 21: aload_2
  214. 22: athrow
  215. 23: return
  216. Exception table:
  217. from to target type
  218. 5 15 18 any
  219. 18 21 18 any
  220. LineNumberTable:
  221. line 35: 0
  222. line 36: 5
  223. line 37: 13
  224. line 38: 23
  225. StackMapTable: number_of_entries = 2
  226. frame_type = 255 /* full_frame */
  227. offset_delta = 18
  228. locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ]
  229. stack = [ class java/lang/Throwable ]
  230. frame_type = 250 /* chop */
  231. offset_delta = 4
  232. }
  233. SourceFile: "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 锁。

  1. 0: aload_0 /** 加载当前 this 对象 */
  2. 1: getfield #3 /** 获取 this 对象的实例变量 lock */ // Field lock:Ljava/lang/Object;
  3. 4: dup /** 将实例变量 lock 压入栈顶 */
  4. 5: astore_1 /** 从栈顶取出 lock 对象 */
  5. 6: monitorenter /** 获取 lock 对象的 monitor 锁 */

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

  1. 0: ldc #11 /** 从常量池中获取 SynchronizedPrincipleTest 类对象 */ // class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
  2. 2: dup /** 将 Class 对象压入栈顶 */
  3. 3: astore_1 /** 从栈顶取出 Class 对象 */
  4. 4: monitorenter /** 获取 Class 对象的 monitor 锁 */

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

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

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

推荐阅读

synchronized 代码块怎么用

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

加不加 synchronized 有什么区别?

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

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

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

线程最最基础的知识

老板叫你别阻塞了

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

泡一杯茶,学一学同异步

进程知多少?

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

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

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

synchronized 的实现原理的更多相关文章

  1. Java并发编程:Synchronized及其实现原理

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  2. Synchronized及其实现原理

    并发编程中synchronized一直是元老级角色,我们称之为重量级锁.主要用在三个地方: 1.修饰普通方法,锁是当前实例对象. 2.修饰类方法,锁是当前类的Class对象. 3.修饰代码块,锁是sy ...

  3. synchronized底层实现原理&CAS操作&偏向锁、轻量级锁,重量级锁、自旋锁、自适应自旋锁、锁消除、锁粗化

    进入时:monitorenter 每个对象有一个监视器锁(monitor).当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:1 ...

  4. synchronized的实现原理与应用

    Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令. sync ...

  5. HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的原理和区别

    HashMap 是否是线程安全的,如何在线程安全的前提下使用 HashMap,其实也就是HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的 ...

  6. jdk1.8源码Synchronized及其实现原理

    一.Synchronized的基本使用 关于Synchronized在JVM的原理(偏向锁,轻量级锁,重量级锁)可以参考 :  http://www.cnblogs.com/dennyzhangdd/ ...

  7. 【死磕Java并发】-----深入分析synchronized的实现原理

    记得刚刚開始学习Java的时候.一遇到多线程情况就是synchronized.相对于当时的我们来说synchronized是这么的奇妙而又强大,那个时候我们赋予它一个名字"同步". ...

  8. Java并发—–深入分析synchronized的实现原理

    记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予它一个名字“同步”,也成为了我们解决多线 ...

  9. Synchronized之二:synchronized的实现原理

    Java提供了synchronized关键字来支持内在锁.Synchronized关键字可以放在方法的前面.对象的前面.类的前面. 当线程调用同步方法时,它自动获得这个方法所在对象的内在锁,并且方法返 ...

  10. 【转】Java并发编程:Synchronized及其实现原理

    一.Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步 ...

随机推荐

  1. [codevs2370]小机房的树<LCA>

    题目链接:http://codevs.cn/problem/2370/ 这题我还是做了比较久了,因为有人告诉我这是用tarjan离线做 好吧算我是蒟蒻,真心不懂tarjan怎么做,最后还是用倍增做的 ...

  2. ASP.NET Core技术研究-探秘Host主机启动过程

    当我们将原有ASP.NET 应用程序升级迁移到ASP.NET Core之后,我们发现代码工程中多了两个类Program类和Startup类. 接下来我们详细探秘一下通用主机Host的启动过程. 一.P ...

  3. 【tensorflow2.0】张量的结构操作

    张量的操作主要包括张量的结构操作和张量的数学运算. 张量结构操作诸如:张量创建,索引切片,维度变换,合并分割. 张量数学运算主要有:标量运算,向量运算,矩阵运算.另外我们会介绍张量运算的广播机制. 本 ...

  4. 原理解密 → Spring AOP 实现动态数据源(读写分离),底层原理是什么

    开心一刻 女孩睡醒玩手机,收到男孩发来一条信息:我要去跟我喜欢的人表白了! 女孩的心猛的一痛,回了条信息:去吧,祝你好运! 男孩回了句:但是我没有勇气说不来,怕被打! 女孩:没事的,我相信你!此时女孩 ...

  5. Spring(一):Spring入门程序和IoC初步理解

    本文是按照狂神说的教学视频学习的笔记,强力推荐,教学深入浅出一遍就懂!b站搜索狂神说或点击下面链接 https://space.bilibili.com/95256449?spm_id_from=33 ...

  6. IO 模型知多少

    1. 引言 同步异步I/O,阻塞非阻塞I/O是程序员老生常谈的话题了,也是自己一直以来懵懵懂懂的一个话题.比如:何为同步异步?何为阻塞与非阻塞?二者的区别在哪里?阻塞在何处?为什么会有多种IO模型,分 ...

  7. webpack4.0(01.基础配置和初识)

    1.什么是webpack? 2.webpack可以做什莫? 代码转换.文件优化.代码分割.模块合并.自动刷新.代码校验.自动发布 3.我们要学习webpack的什么? 4.使用webpack 1.首先 ...

  8. Python 的while循环和for循环的使用

    #循环 遍历 迭代 # while循环 a = 0while a <5: a =a+1 if a == 3: continueprint('我循环了')+str(a) # print ('我循环 ...

  9. 06-jmeter参数化(函数对话框使用)

    概念: 1.变量命名的规则:字母.下划线开头,可包含数字,严格区分大小写 2.配置元件:用户定义的变量-------值是不变化的 用户命名的参数--------可以动态获取的并传参的 jmeter函数 ...

  10. search(4)- elastic4s-ElasticDsl

    上次分析了一下elastic4s的运算框架.本来计划接着开始实质的函数调用示范,不过看过了Elastic4s的所有使用说明文档后感觉还是走的快了一点.主要原因是elasticsearch在7.0后有了 ...