Synchronized实现原理

Synchronized实现同步的基础:java的每一个对象都可以作为锁

3中表现形式

  • 普通同步方法:锁是当前实例对象
  • 静态同步方法:锁是当前类的Class对象
  • 同步方法块:锁是Synchronized括号里配置的对象

从JVM看实现原理

JVM是基于进入和退出Monitor对象实现方法同步和代码块同步,但二者实现细节不同。

代码块同步是基于使用monitorenter和monitorexit指令实现的,而方法同步基于另一种(也可以使用这两个指令实现)

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

synchronize锁的升级

java SE1.6中,锁一共有4种状态,由低到高是:无锁态、偏向锁、轻量级锁、重量级锁锁只能升级但不能降级不能降级是为了提高获得锁和释放锁的效率

  1. 偏向锁

    HotSpot作者经研究发现,多数情况,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,为了使此线程获得锁的代价更低而引入了偏向锁

    • 获得锁过程

      线程进入同步块并获得锁时,在对象头和帧栈中的锁记录里存储所偏向的线程ID。以后线程进入和退出同步块时就不需要CAS操作进行加解锁,只需测试对象头的mark word是否存着指向当前线程的偏向锁(线程ID、是否偏向锁位)是,表示线程已获得锁,否则,测试mark word中偏向锁表示是否为1,不是则使用CAS竞争锁,否则尝试使用CAS将对象头的线程ID设置为本线程

    • 释放锁过程

      偏向锁使用一种等到竞争出现才释放锁的机制。释放需要等待全局安全点(此时没有正在执行的字节码),先暂停持有偏向锁的线程,检查持有偏向锁的线程是否活着(run或者runnable),否,则设置对象头为无锁,遍历偏向对象的锁记录栈中的锁记录和对象头的mark word要么重新偏向其他线程,要么恢复到无锁标志对象不适合作为偏向锁,唤醒被暂停的线程;是,拥有偏向锁的线程继续执行,偏向锁升级为轻量级锁。

    • 关闭偏向锁

      java 6和java 7默认启动,关闭会默认进入轻量级锁

  2. 轻量级锁

    • 加锁过程

      进入同步块时,若同步对象为无锁态,虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word 并复制其到锁记录空间。虚拟机使用CAS操作尝试将对象的Mark Word更新为指向线程的指针,即用Lock record里的owner指针代替对象的 mark word(Lock Record包含Displaced Mark Word和owner,owner代指线程本身ID)。
      替换成功,当前线程获得锁,否则,表示有竞争,当前线程尝试自旋来获取锁

    • 释放锁过程

      使用CAS操作将Displaced Mark Word替换回原对象,成功,表示无竞争,否则,锁会膨胀为重量级锁

    • 关于自旋

      自旋会消耗CPU,为了避免无用的自旋(如已获得锁的线程被阻塞),因而升级成重量级锁,也就是从忙等待到了睡眠状态,不会消耗CPU,仅当持有锁的线程释放了才会唤醒其他等锁线程

  3. 重量级锁

    最原始的锁,休眠唤醒机制,造成相应较慢。

重量级锁、轻量级锁和偏向锁之间转换

Synchronized实现原理_第1张图片
H3.png

锁的优缺点

类别 优点 缺点 适用场景
偏向锁 加锁解锁不需要额外的消耗,和执行非同步的方法相比仅存在纳秒级的差距 如果线程间存在锁竞争, 则会带来额外的锁撤销的消耗 适用于长期只有一个线程访问同步块的场景
轻量级锁 竞争锁的线程不会阻塞, 提高了程序的响应速度 竞争的线程会自旋, 消耗CPU 追求响应时间, 且同步块执行速度快的场景
重量级锁 竞争锁的线程不自旋, 不会消耗CPU 竞争锁的线程会阻塞, 响应时间缓慢 追求吞吐量, 或者是同步块执行时间较长的时候

参考:

https://blog.csdn.net/zhoufanyang_china/article/details/54601311
《java并发编程的艺术》

你可能感兴趣的:(Synchronized实现原理)