深入底层讲解:从 Synchronized 到锁的优化全过程解析

本文深入探讨了Java中的Synchronized关键字,从其原理到锁的优化策略,包括监视器锁(monitor)、偏向锁、轻量级锁和重量级锁。解释了Synchronized如何保证并发安全,以及为何被称为重量级锁,并介绍了锁的升级过程,如锁消除和锁粗化。此外,文章还讨论了自旋锁和自适应自旋锁在提高性能中的作用。
摘要由CSDN通过智能技术生成

我们知道 SynchronizedJava 中解决并发问题的一种最常用的方法, 也是最简单的一种方法. 被也被称为内置锁.

Synchronized 的作用主要有三个:

  • 确保线程互斥的访问同步代码
  • 保证共享变量的修改能够及时可见
  • 有效解决重排序问题。

从语法上讲, Synchronized 总共有三种用法:

  • 修饰普通方法, 锁是当前实例对象.
  • 修饰静态方法, 锁是当前类的 class 对象.
  • 修饰代码块, 锁是括号中的对象.

关于使用方式, 这里就不再进行一一描述了. 我们直接进入正题, 看 Synchronized 的底层实现原理是什么.

1. Synchronized 原理

首先, 我们先来看一段代码, 使用了同步代码块和同步方法, 通过使用 javap 工具查看生成的 class 文件信息来分析 synchronized 关键字的实现细节.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6O3iT05F-1615815512334)(//upload-images.jianshu.io/upload_images/7115372-3a18801ac85d8535.png?imageMogr2/auto-orient/strip|imageView2/2/w/592/format/webp)]

对代码进行反编译后的结果如下

  public static void main(java.lang.String[]) throws java.lang.Exception;
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: getstatic    
         3: dup
         4: astore_1
         5: monitorenter  //---------------------------------------------1.
         6: aload_1
         7: monitorexit    //---------------------------------------------2.
         8: goto          16
        11: astore_2
        12: aload_1
        13: monitorexit   //---------------------------------------------3.
        14: aload_2
        15: athrow
        16: return
        ...

  public static synchronized void test();
    descriptor: ()V
    flags: (0x0029) ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED //---------------------------------------------4.
    Code:
      stack=0, locals=0, args_size=0
         0: return
      LineNumberTable:
        line 21: 0

从生产的 class 信息中, 可以清楚的看到两部分内容

  • 同步代码块中使用了 monitorentermonitorexit 指令.
  • 同步方法中依靠方法修饰符 flags 上的 ACC_SYNCHRONIZED 实现.

先看反编译出 main 方法中标记的 1 与 2. monitorenter / monitorexit 关于这两条指令的作用, 参考 JVM 中对他们的描述如下:

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

  • 如果 monitor 的进入数为 0 , 则该线程进入 monitor, 然后将进入数设置为 1, 该线程即为 monitor 的拥有者.
  • 如果线程已经占有该 monitor, 只是重新进入, 则进入 monitor 的进入数加 1.
  • 如果其他线程已经占用了 monitor, 则该线程进入阻塞状态, 直到 monitor 的进入数为 0, 再尝试获取 monitor 的所有权.

monitorexit
执行 monitorexit 的线程必须是对应 monitor的所有者. 执行指令时, monitor的进入数减 1. 如果减 1 后进入数为 0, 则线程退出 monitor. 不再是这个 monitor 的所有者. 其他被这个 monitor 阻塞的线程可以尝试去获取这个 monitor 的所有权.

monitorenter 指令是在编译后插入到同步代码块开始的位置, 而 monitorexit 是插入到方法的结束处和异常处. 这也就是为什么在 3 处会单独有一个 monitorexit 了.

ACC_SYNCHRONIZED
当方法调用时, 调用指令将检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置, 如果设置了, 执行线程将先获取 monitor, 获取成功之后才能执行方法体. 方法执行完后再释放 monitor, 在方法执行期间, 其他任何线程都无法再获得同一个 monitor 对象.
其实这个和上面 monitorentermonitorexit 本质上没有区别, 只是方法的同步是一种隐式的方式来实现的, 无需通过字节码来完成.

看完这些, 是不是觉得有点和 AQS 中的 state 相似? 如果看完了 从 LockSupport 到 AQS 的简单学习 这篇文章的朋友, 再来看这里, 我相信应该会很容易理解.

这里既然说到了监视器锁 monitor , 我们一起来看这

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值