• 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html
  • 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html

Synchronize 关键字原理

技术杂谈 勤劳的小蚂蚁 3个月前 (01-25) 91次浏览 已收录 0个评论 扫描二维码

众所周知 Synchronize 关键字是解决并发问题常用解决方案,有以下三种使用方式:
  • 同步普通方法,锁的是当前对象。
  • 同步静态方法,锁的是当前 Class 对象。
  • 同步块,锁的是 {} 中的对象。
实现原理: JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。
具体实现是在编译之后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令。
其本质就是对一个对象监视器( Monitor )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。
而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit 之后才能尝试继续获取锁。
流程图如下:
通过一段代码来演示:
  1.    publicstaticvoid main(String[] args){
  2.        synchronized(Synchronize.class){
  3.            System.out.println("Synchronize");
  4.        }
  5.    }
使用 javap-cSynchronize 可以查看编译之后的具体信息。
  1. publicclass com.crossoverjie.synchronize.Synchronize{
  2.  public com.crossoverjie.synchronize.Synchronize();
  3.    Code:
  4.       0: aload_0
  5.       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
  6.       4:return
  7.  publicstaticvoid main(java.lang.String[]);
  8.    Code:
  9.       0: ldc           #2                  // class com/crossoverjie/synchronize/Synchronize
  10.       2: dup
  11.       3: astore_1
  12.       **4: monitorenter**
  13.       5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
  14.       8: ldc           #4                  // String Synchronize
  15.      10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  16.      13: aload_1
  17.      **14: monitorexit**
  18.      15:goto          23
  19.      18: astore_2
  20.      19: aload_1
  21.      20: monitorexit
  22.      21: aload_2
  23.      22: athrow
  24.      23:return
  25.    Exception table:
  26.       from    to  target type
  27.           5    15    18   any
  28.          18    21    18   any
  29. }
可以看到在同步块的入口和出口分别有 monitorenter,monitorexit指令。

锁优化

synchronize 很多都称之为重量锁, JDK1.6 中对 synchronize 进行了各种优化,为了能减少获取和释放锁带来的消耗引入了 偏向锁轻量锁

轻量锁

当代码进入同步块时,如果同步对象为无锁状态时,当前线程会在栈帧中创建一个锁记录( LockRecord)区域,同时将锁对象的对象头中 MarkWord 拷贝到锁记录中,再尝试使用 CASMarkWord 更新为指向锁记录的指针。
如果更新成功,当前线程就获得了锁。
如果更新失败JVM 会先检查锁对象的 MarkWord 是否指向当前线程的锁记录。
如果是则说明当前线程拥有锁对象的锁,可以直接进入同步块。
不是则说明有其他线程抢占了锁,如果存在多个线程同时竞争一把锁,轻量锁就会膨胀为重量锁

解锁

轻量锁的解锁过程也是利用 CAS 来实现的,会尝试锁记录替换回锁对象的 MarkWord 。如果替换成功则说明整个同步操作完成,失败则说明有其他线程尝试获取锁,这时就会唤醒被挂起的线程(此时已经膨胀为 重量锁)
轻量锁能提升性能的原因是:认为大多数锁在整个同步周期都不存在竞争,所以使用 CAS 比使用互斥开销更少。但如果锁竞争激烈,轻量锁就不但有互斥的开销,还有 CAS 的开销,甚至比重量锁更慢。

偏向锁

为了进一步的降低获取锁的代价, JDK1.6 之后还引入了偏向锁。
偏向锁的特征是:锁不存在多线程竞争,并且应由一个线程多次获得锁。
当线程访问同步块时,会使用 CAS 将线程 ID 更新到锁对象的 MarkWord 中,如果更新成功则获得偏向锁,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。

释放锁

当有另外一个线程获取这个锁时,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来判定将对象头中的 MarkWord 设置为无锁或者是轻量锁状态。
轻量锁可以提高带有同步却没有竞争的程序性能,但如果程序中大多数锁都存在竞争时,那偏向锁就起不到太大作用。可以使用 -XX:-userBiasedLocking=false 来关闭偏向锁,并默认进入轻量锁。

其他优化

适应性自旋

在使用 CAS 时,如果操作失败, CAS 会自旋再次尝试。由于自旋是需要消耗 CPU 资源的,所以如果长期自旋就白白浪费了 CPUJDK1.6加入了适应性自旋:
如果某个锁自旋很少成功获得,那么下一次就会减少自旋。


丨极客文库, 版权所有丨如未注明 , 均为原创丨
本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
转载请注明原文链接:Synchronize 关键字原理
喜欢 (0)
[247507792@qq.com]
分享 (0)
勤劳的小蚂蚁
关于作者:
温馨提示:本文来源于网络,转载文章皆标明了出处,如果您发现侵权文章,请及时向站长反馈删除。

您必须 登录 才能发表评论!

  • 精品技术教程
  • 编程资源分享
  • 问答交流社区
  • 极客文库知识库

客服QQ


QQ:2248886839


工作时间:09:00-23:00