Synchronized

举报
Faker 发表于 2021/03/23 17:50:32 2021/03/23
【摘要】 我们知道 Synchronized 是 Java 中解决并发问题的一种最常用的方法, 也是最简单的一种方法. 被也被称为内置锁.Synchronized 的作用主要有三个:确保线程互斥的访问同步代码保证共享变量的修改能够及时可见有效解决重排序问题。 从语法上讲, Synchronized 总共有三种用法:修饰普通方法, 锁是当前实例对象.修饰静态方法, 锁是当前类的 class 对象.修饰代...

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

Synchronized 的作用主要有三个:

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

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

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

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

1. Synchronized 原理

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


代码片段

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


  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 的简单学习 这篇文章的朋友, 再来看这里, 我相信应该会很容易理解.


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。