• jdk-synchronized源码学习


    synchronized介绍

        java中jdk1.6之前和jdk1.6及之后synchronized完全不一样。1.6之前直接上来都是重量级锁导致java的性能很低效,而1.6及之后甲骨文公司对其进行优化,通过一个锁的升级过程从而来支持一些非复杂的场景。那么本文主要是针对synchronized的源码及一些使用进行了解。

    java的内存布局和对象头及锁升级过程

    请查看之前发布过的文章:对象实例化与内存布局(深入)

    加锁的三种方式及锁的粒度

    加到静态方法,锁是当前类对象

    1. public synchronized static  void method(){
    2.     System.out.println("锁的是对象");
    3. }

    同步类方法,锁是当前方法

    1. public synchronized void method2(){
    2.     System.out.println("锁的是方法!");
    3. }

    同步代码块,锁是括号里面的对象

    1. public  void method1(){
    2.     synchronized (this){
    3.         System.out.println("锁是代码块!");
    4.     }
    5. }

    查看锁

    1. public class SynchronizedLock {
    2.     public synchronized void method(){
    3.         System.out.println("同步锁测试!");
    4.     }
    5. }

    javap -c -v  SynchronizedLock.class

    1. Classfile /Users/csh/ideaworkspace/jdk8/src/main/java/com/lock/SynchronizedLock.class
    2.   Last modified 2022-11-20; size 512 bytes
    3.   MD5 checksum c1a83fca01614297b7553ecf31e0b979
    4.   Compiled from "SynchronizedLock.java"
    5. public class com.lock.SynchronizedLock
    6.   minor version: 0
    7.   major version: 52
    8.   flags: ACC_PUBLIC, ACC_SUPER
    9. Constant pool:
    10.    #1 = Methodref #7.#16 // java/lang/Object."":()V
    11.    #2 = Fieldref #17.#18 // java/lang/System.out:Ljava/io/PrintStream;
    12.    #3 = String #19 // 同步锁测试!
    13.    #4 = Methodref #20.#21 // java/io/PrintStream.println:(Ljava/lang/String;)V
    14.    #5 = String #22 // 普通方法!
    15.    #6 = Class #23 // com/lock/SynchronizedLock
    16.    #7 = Class #24 // java/lang/Object
    17.    #8 = Utf8
    18.    #9 = Utf8 ()V
    19.   #10 = Utf8 Code
    20.   #11 = Utf8 LineNumberTable
    21.   #12 = Utf8 method
    22.   #13 = Utf8 method2
    23.   #14 = Utf8 SourceFile
    24.   #15 = Utf8 SynchronizedLock.java
    25.   #16 = NameAndType #8:#9 // "":()V
    26.   #17 = Class #25 // java/lang/System
    27.   #18 = NameAndType #26:#27 // out:Ljava/io/PrintStream;
    28.   #19 = Utf8 同步锁测试!
    29.   #20 = Class #28 // java/io/PrintStream
    30.   #21 = NameAndType #29:#30 // println:(Ljava/lang/String;)V
    31.   #22 = Utf8 普通方法!
    32.   #23 = Utf8 com/lock/SynchronizedLock
    33.   #24 = Utf8 java/lang/Object
    34.   #25 = Utf8 java/lang/System
    35.   #26 = Utf8 out
    36.   #27 = Utf8 Ljava/io/PrintStream;
    37.   #28 = Utf8 java/io/PrintStream
    38.   #29 = Utf8 println
    39.   #30 = Utf8 (Ljava/lang/String;)V
    40. {
    41.   public com.lock.SynchronizedLock();
    42.     descriptor: ()V
    43.     flags: ACC_PUBLIC
    44.     Code:
    45.       stack=1, locals=1, args_size=1
    46.          0: aload_0
    47.          1: invokespecial #1 // Method java/lang/Object."":()V
    48.          4: return
    49.       LineNumberTable:
    50.         line 8: 0
    51.   public synchronized void method();
    52.     descriptor: ()V
    53.     //注意:ACC_SYNCHRONIZED 是用来标识是同步方法
    54.     flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    55.     Code:
    56.       stack=2, locals=1, args_size=1
    57.          0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
    58.          3: ldc #3 // String 同步锁测试!
    59.          5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    60.          8: return
    61.       LineNumberTable:
    62.         line 10: 0
    63.         line 11: 8
    64.   public void method2();
    65.     descriptor: ()V
    66.     flags: ACC_PUBLIC
    67.     Code:
    68.       stack=2, locals=1, args_size=1
    69.          0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
    70.          3: ldc #5 // String 普通方法!
    71.          5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    72.          8: return
    73.       LineNumberTable:
    74.         line 14: 0
    75.         line 15: 8
    76. }

    ad84e0d1d5f0e4a04c0fcb30e1f7f0bd.png

    从上面可以看出来普通的方法和同步方法,在字节码层面同步方法多了一个ACC_SYNCHRONIZED 从而来使jvm知道这个方法是同步方法。

    什么是monitor对象?

    f1fce4e6bb05455734fe392a849b4f96.png

    首先monitor叫作管程或监视器,用于创建监视锁,每个对象都存在一个monitor关联的对象,包含monitorenter和monitorexit。

    个人理解:这个就是监控,你干啥干啥了。

    流程是这样的:

    e13396df099db106ce7632a9cd71fa57.png

        如果当进入的时候monitor为0,则代表没有被持有,会将这个monitor计数器设置为1;

        如果进来的时候线程为该monitor的持有者,那么计数器加1(可重入);

        如果该线程不是该monitor的持有者,进来获取若已被其它线程持有,那么获取monitor失败,进入阻塞状态,直到monitor计数器为0,其它线程退出Monitor,重新唤醒同步队列中的线程;

    为什么会用两个MonitorExit指令?

        第一个退出指令为正常退出,第二个为异常退出。

    源码阅读

    ObjectMonitor.hpp(C++代码)

    1. ObjectMonitor() {
    2.    _header = NULL; //头部默认为空
    3.    _count = 0; //记录个数
    4.    _waiters = 0, //等待中的线程数
    5.    _recursions = 0; //线程重入次数
    6.    _object = NULL; //存储该monitor的对象
    7.    _owner = NULL; //拥有该monitor的线程
    8.    _WaitSet = NULL; //等待线程的列表
    9.    _WaitSetLock = 0 ;
    10.    _Responsible = NULL ;
    11.    _succ = NULL ;
    12.    _cxq = NULL ; //多线程竟争锁时的单向链表
    13.    FreeNext = NULL ;
    14.    _EntryList = NULL ; //待锁列表(入口列表)
    15.    _SpinFreq = 0 ;
    16.    _SpinClock = 0 ;
    17.    OwnerIsThread = 0 ;
    18.    _previous_owner_tid = 0; // 前一个拥有此监视器的线程 ID
    19.  }

    注意这里:_WaitSet和_EntryList是ObjectMonitor的两个主要队列,分别用来存放等待队列和正在执行的集合。

    多线程时的执行流程:

    先进入_EntryList集合,然后线程去获取monitor,若成功获取则进入_Owner并把当前线程设置为owner,同时Monitor中的计数器count加1(_count);

    若线程执行过程中调用了wait()方法,当前线程就会释放持有的monitor,这时_owner重新被置为null,count减1,同时该线程被放入_WaitSet列表中等待被唤醒。

    若线程执行完毕,那么默认会释放monitor(锁)并将count的值减1或置为0,让出这个锁并且唤醒_WaitSet队列中的线程。

    hostpot中有两种解释器模板解释器和字节码解释器,其中字节码是基于汇偏于言,虚拟机早期开发的一种解释器(很慢的原因,一行一行读的),而模板是正在正在解释使用的另外一种高效快速解释器。(不懂解释器请看之前文章!)

    不过模板解释器基于汇编来写的(性能高啊~)比较难读,而字节码解释器是C++代码写的。

    以下来源于:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/interpreter/bytecodeInterpreter.cpp#l1816

    实际不会读到这个bytecodeInterpreter.cpp而是读到templateTable_x86_64.cpp

    395827b60952ed7584a3f5dd1ddd4baa.png

    87c624b1dcfc401e2f69fbfb876e36c1.png

    以下代码位置:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/cpu/x86/vm/templateTable_x86_64.cpp#l3667

    8154344faab51cb046f894d67c635baa.png

    1bdeadff92f2b32b4209d6654e30bc8d.png

    这个基于汇编语言的,的确纯阅读起来,太费解了,还是需要一行一行来调试会更易理解,想想这个学汇编都十几年前的事了~,还是建议看看c++吧。

    最后

        由于时间问题及考虑到把所有c++或汇编源码贴出来没什么意思,建议有需要深入了解同步锁,锁升级过程的源码同学可以看看下面的参考文章,里面有些大佬已经把详细步骤及过程写出来了,可以深入再学学。关于虚拟机的汇编源码我本人是不建议看,因为代码量巨大还有耗费的时间可能需要最少长达半个月或几个月才能消化完,所以有这个时间都可以学一个新的技术点了,当然有需要深入或工作上需要的同学那建议看看下面的参考文章可以减少不少的学习弯路。

    参考文章:

    https://github.com/farmerjohngit/myblog/issues/13

    https://blog.csdn.net/u014454538/article/details/120731549

    https://tech.meituan.com/2018/11/15/java-lock.html

    https://www.pdai.tech/md/java/thread/java-thread-x-key-synchronized.html

    https://xie.infoq.cn/article/3ac3596347f9e914a2d2a3587

    https://xiaomi-info.github.io/2020/03/24/synchronized/

    https://www.cnblogs.com/trunks2008/p/14613090.html

    https://xie.infoq.cn/article/3ac3596347f9e914a2d2a3587

    https://zhuanlan.zhihu.com/p/33830504

    c++实现源码类:

    http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/interpreter/bytecodeInterpreter.cpp

    http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/cpu/x86/vm/templateTable_x86_64.cpp

    http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/interpreter/interpreterRuntime.cpp

  • 相关阅读:
    坚持自学软件测试,半年的辛苦没有白费,不过才拿到10k的offer
    (一)JVM实战——jvm的组成部分详解
    dockfile指令与构建自己的centos镜像与docker镜像历史更变信息
    【Python】取火柴小游戏(巴什博弈)
    c语言基础:L1-052 2018我们要赢
    Hbase 笔记
    『亚马逊云科技产品测评』活动征文|AWS 数据库产品类别及其适用场景详细说明
    强势借力Arbitrum,看代币ARC如何大放异彩
    8K HDR!|为 Chromium 实现 HEVC 硬解 - 原理/实测指南
    小程序员 scroll滚动与页面滚动冲突造成快速滑到底部卡顿失败问题
  • 原文地址:https://blog.csdn.net/qq_16498553/article/details/128059938