【JAVA并发编程系列】并发 -- synchronized 关键字与锁总结

【JAVA并发编程系列】并发 -- synchronized 关键字与锁总结

【1】JAVA 对象头

【2】利用 synchronized 实现同步的基础

Java中的每一个对象都可以作为锁;
具体表现 : 

  • 1. 对于普通同步方法,锁是当前实例对象;
  • 2. 对于静态同步方法,锁是当前类的 Class 对象;
  • 3. 对于同步方法块,锁是 Synchonized 括号里配置的对象;

【3】Synchonized 在 JVM 里的实现原理

  • JVM 基于进入和退出 Monitor 对象来实现方法同步和代码块同步,即使用 monitorenter 和 monitorexit 指令实现的同步;
  • monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 是插入到方法结束处和异常处;
  • JVM 要保证每个 monitorenter 必须有对应的 monitorexit 与之配对;
  • 任何对象都有一个 monitor 与之关联,当一个 monitor 被持有后,它将处于锁定状态;
  • 线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁;

【4】锁的升级与对比
【4.1】偏向锁

偏向锁引入

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁;
偏向锁的获取

  • 当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行 CAS 操作来加锁和解锁,只需简单地测试一下对象头的 Mark Word 里是否存储着指向当前线程的偏向锁;
  • 如果测试成功,表示线程已经获得了锁;
  • 如果测试失败,则需要再测试一下 Mark Word 中偏向锁的标识是否设置成 1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用 CAS 将对象头的偏向锁指向当前线程;

偏向锁的撤销

  • 偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁;
  • 偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码),它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的 Mark Word 要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程;

【4.2】轻量级锁

轻量级锁加锁

  • 线程在执行同步块之前,JVM 会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的 Mark Word 复制到锁记录中,官方称为 Displaced Mark Word,然后线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向锁记录的指针,如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁;

轻量级锁解锁

  • 轻量级解锁时,会使用原子的 CAS 操作将 Displaced Mark Word 替换回到对象头,如果成功,则表示没有竞争发生,如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁;

注意

  • 因为自旋会消耗 CPU,为了避免无用的自旋,一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态;
  • 当锁处于重量级状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争;

【4.3】偏向锁、轻量级锁、重量级锁的优缺点对比

【5】锁的内存语义

锁的释放-获取建立的 happens-before 关系

  • 锁是 Java 并发编程中最重要的同步机制,锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息;

锁的释放和获取的内存语义

  • 当线程释放锁时,JMM 会把该线程对应的本地内存中的共享变量刷新到主内存中;
  • 当线程获取锁时,JMM 会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量;

  • 1. 线程 A 释放一个锁,实质上是线程 A 向接下来将要获取这个锁的某个线程发出了(线程 A 对共享变量所做修改的)消息;
  • 2. 线程 B 获取一个锁,实质上是线程 B 接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息;
  • 3. 线程 A 释放锁,随后线程 B 获取这个锁,这个过程实质上是线程 A 通过主内存向线程 B 发送消息;

参考致谢
本博客为博主的学习实践总结,并参考了众多博主的博文,在此表示感谢,博主若有不足之处,请批评指正。

【1】Java并发编程的艺术

【2】CAS操作、Java对象头、偏向锁的获取与撤销、轻量级锁的获取与撤销、锁粗化、锁消除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值