加不加 synchronized 有什么区别?

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

synchronized 代码块怎么用

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

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

public class SynchronizedPrincipleTest {

    public void testNoSynchronized() {
System.out.println("hello testNoSynchronized");
} public synchronized void testSynchronizedMethod() {
System.out.println("hello testSynchronizedMethod");
} public static synchronized void testSynchronizedStatic() {
System.out.println("hello testSynchronizedStatic");
} public void testSynchronizedCodethis() {
synchronized (this) {
System.out.println("hello testSynchronizedCode");
}
} private Object lock = new Object();
public void testSynchronizedCodeObject() {
synchronized (lock) {
System.out.println("hello testSynchronizedCodeObject");
}
} public void testSynchronizedCodeClass() {
synchronized (SynchronizedPrincipleTest.class) {
System.out.println("hello testSynchronizedCodeClass");
}
}
}

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

javac SynchronizedPrincipleTest.java
javap -v SynchronizedCodeTest.class

得出我们要汇编代码。

Classfile /D:/Workspace/finance/test/thread/src/main/java/com/liebrother/study/synchronizeds/SynchronizedPrincipleTest.class
Last modified Apr 26, 2020; size 1363 bytes
MD5 checksum a03ec0b152580bb465b1defe7965a60d
Compiled from "SynchronizedPrincipleTest.java"
public class com.liebrother.study.synchronizeds.SynchronizedPrincipleTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #2.#31 // java/lang/Object."<init>":()V
#2 = Class #32 // java/lang/Object
#3 = Fieldref #11.#33 // com/liebrother/study/synchronizeds/SynchronizedPrincipleTest.lock:Ljava/lang/Object;
#4 = Fieldref #34.#35 // java/lang/System.out:Ljava/io/PrintStream;
#5 = String #36 // hello testNoSynchronized
#6 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V
#7 = String #39 // hello testSynchronizedMethod
#8 = String #40 // hello testSynchronizedStatic
#9 = String #41 // hello testSynchronizedCode
#10 = String #42 // hello testSynchronizedCodeObject
#11 = Class #43 // com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
#12 = String #44 // hello testSynchronizedCodeClass
#13 = Utf8 lock
#14 = Utf8 Ljava/lang/Object;
#15 = Utf8 <init>
#16 = Utf8 ()V
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 testNoSynchronized
#20 = Utf8 testSynchronizedMethod
#21 = Utf8 testSynchronizedStatic
#22 = Utf8 testSynchronizedCodethis
#23 = Utf8 StackMapTable
#24 = Class #43 // com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
#25 = Class #32 // java/lang/Object
#26 = Class #45 // java/lang/Throwable
#27 = Utf8 testSynchronizedCodeObject
#28 = Utf8 testSynchronizedCodeClass
#29 = Utf8 SourceFile
#30 = Utf8 SynchronizedPrincipleTest.java
#31 = NameAndType #15:#16 // "<init>":()V
#32 = Utf8 java/lang/Object
#33 = NameAndType #13:#14 // lock:Ljava/lang/Object;
#34 = Class #46 // java/lang/System
#35 = NameAndType #47:#48 // out:Ljava/io/PrintStream;
#36 = Utf8 hello testNoSynchronized
#37 = Class #49 // java/io/PrintStream
#38 = NameAndType #50:#51 // println:(Ljava/lang/String;)V
#39 = Utf8 hello testSynchronizedMethod
#40 = Utf8 hello testSynchronizedStatic
#41 = Utf8 hello testSynchronizedCode
#42 = Utf8 hello testSynchronizedCodeObject
#43 = Utf8 com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
#44 = Utf8 hello testSynchronizedCodeClass
#45 = Utf8 java/lang/Throwable
#46 = Utf8 java/lang/System
#47 = Utf8 out
#48 = Utf8 Ljava/io/PrintStream;
#49 = Utf8 java/io/PrintStream
#50 = Utf8 println
#51 = Utf8 (Ljava/lang/String;)V
{
public com.liebrother.study.synchronizeds.SynchronizedPrincipleTest();
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/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."<init>":()V
12: putfield #3 // Field lock:Ljava/lang/Object;
15: return
LineNumberTable:
line 7: 0
line 27: 4 /** 无 synchronized 修饰的代码 */
public void testNoSynchronized();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String hello testNoSynchronized
5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 10: 0
line 11: 8 /** synchronized 修饰的实例方法 */
public synchronized void testSynchronizedMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED /** 方法标识多了一个 ACC_SYNCHRONIZED */
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #7 // String hello testSynchronizedMethod
5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 14: 0
line 15: 8 /** synchronized 修饰的静态方法 */
public static synchronized void testSynchronizedStatic();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED /** 方法标识多了 ACC_STATIC 和 ACC_SYNCHRONIZED */
Code:
stack=2, locals=0, args_size=0
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #8 // String hello testSynchronizedStatic
5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 18: 0
line 19: 8 /** synchronized 修饰的 this 代码块 */
public void testSynchronizedCodethis();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter /** 通过 monitorenter 命令进入监视器锁 */
4: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #9 // String hello testSynchronizedCode
9: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit /** 通过 monitorexit 命令退出监视器锁 */
14: goto 22
17: astore_2
18: aload_1
19: monitorexit /** 通过 monitorexit 命令退出监视器锁 */
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
LineNumberTable:
line 22: 0
line 23: 4
line 24: 12
line 25: 22
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 17
locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4 /** synchronized 修饰的 object 代码块 */
public void testSynchronizedCodeObject();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field lock:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter /** 通过 monitorenter 命令进入监视器锁 */
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #10 // String hello testSynchronizedCodeObject
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: aload_1
16: monitorexit /** 通过 monitorexit 命令退出监视器锁 */
17: goto 25
20: astore_2
21: aload_1
22: monitorexit /** 通过 monitorexit 命令退出监视器锁 */
23: aload_2
24: athrow
25: return
Exception table:
from to target type
7 17 20 any
20 23 20 any
LineNumberTable:
line 29: 0
line 30: 7
line 31: 15
line 32: 25
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 20
locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4 /** synchronized 修饰的 xxx.Class 代码块 */
public void testSynchronizedCodeClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #11 // class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
2: dup
3: astore_1
4: monitorenter /** 通过 monitorenter 命令进入监视器锁 */
5: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #12 // String hello testSynchronizedCodeClass
10: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit /** 通过 monitorexit 命令退出监视器锁 */
15: goto 23
18: astore_2
19: aload_1
20: monitorexit /** 通过 monitorexit 命令退出监视器锁 */
21: aload_2
22: athrow
23: return
Exception table:
from to target type
5 15 18 any
18 21 18 any
LineNumberTable:
line 35: 0
line 36: 5
line 37: 13
line 38: 23
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 18
locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
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 锁。

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

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

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

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

0: ldc           #11 /** 从常量池中获取 SynchronizedPrincipleTest 类对象 */                 // class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
2: dup /** 将 Class 对象压入栈顶 */
3: astore_1 /** 从栈顶取出 Class 对象 */
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. 玩转控件:封装Dev的SearchLookupEdit

    鸣谢 随着前面几个章节对控件封装与扩展的分享,不少小伙伴儿们在作者公众号上反馈,并联系作者,表示通过这些系列和源码能学到不少细节上的东西,并运用到了自己的实际项目当中,也有不少伙伴儿反馈更好更优的处理 ...

  2. WPF使用 Gmap.NET 绘制极坐标运动轨迹

    大家好,已经很久没有更新了,今天写一篇关于WPF 使用 Gmap.NET 相关的,网上很多Winform的很**,所以我给Wpf进行一些补充.虽然它已经很久没有更新了,但是也只能用这个了.没别的好选择 ...

  3. 解决浏览器看不到Flash文档(尤其某慕课)

    最近遇到很多朋友说浏览器看不到网课资源的文档等等,就顺手写一篇说一下情况 为什么会文档空白 某课网站上面的文档是用flash进行展示的,同时flash被很多浏览器逐步抛弃(快凉了,都是H5了) fla ...

  4. Mac电脑之间的文件共享 - 偏门

    文件共享是工作中经常要进行的. Mac用户之间可以通过AirDrop来共享文件.AirDrop要借助无线网络,而很多人都是将Mac做成个人热点供手机等Wifi连接,AirDrop时必须断开热点,不方便 ...

  5. 001_manifest.json手册

    manifest.json 是一个 JSON 格式的文件,是每个 WebExtension必须包含的唯一文件. 使用manifest.json,您可以指定扩展名的基本元数据,如名称和版本,还可以指定扩 ...

  6. day 1 硬件组成概念及介绍笔记

    一.服务器的种类: 硬件服务器: 1.机架式服务器 2.刀片式服务器 3.塔式服务器 虚拟服务器: 阿里云 aws 腾讯云 二.详细硬件组成: 1.电源  ----心脏(供电) 冗余特性    ups ...

  7. AJAX对数据库增删改查实例

    前端代码: <!DOCTYPE html><html><head><meta charset="UTF-8"><title&g ...

  8. GIT本地安装及汉化

    GIT本地安装及汉化过程 1.下载地址: 链接:https://pan.baidu.com/s/1TMxxngZy4Y1De5eC1kSTMg 提取码:e593 2.下载完成之后如下图所示3个文件: ...

  9. rest_framework-分页

    分页 from django.shortcuts import render # Create your views here. from rest_framework import serializ ...

  10. Linux学习,ACL权限管理

    1.setfacl 得到指定文件的ACL权限 -m       表示后续有参数,不可与 -x参数配合使用 -x         删除后续的acl参数,不可与 -m 配合使用 -b        删除所 ...