晋中美食:synchronized 的实现原理

admin/2020-04-29/ 分类:科技/阅读:

加不加 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线程优先级,更要知道对应操作系统的优先级,否则会踩坑

线程最最基础的知识

老板叫你别壅闭了

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

泡一杯茶,学一学同异步

历程知多少?

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

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

以为文章有用协助转发&点赞,多谢朋友们!

,

sunbet

www.aLizhiye.com自1992年和阿里纸业合作以来,在资金实力、技术体系、贴心服务实现了质的飞跃。阿里纸业作为Sunbet亚洲独家代理,为官网下所有会员开户、代理提供买分卖分等业务。

TAG:
阅读:
广告 330*360
广告 330*360
Sunbet_进入申博sunbet官网
微信二维码扫一扫
关注微信公众号
新闻自媒体 Copyright © 2002-2019 Sunbet 版权所有
二维码
意见反馈 二维码