Java SE1.6对synchronized进行了优化

Java中每一个对象都可以作为锁。具体表现为以下三种形式。

  • 普通同步方法,锁是当前实例对象。
  • 静态同步方法,锁是当前类的Class对象
  • 同步代码块,锁是Synchronized括号中的对象

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或者抛出异常时必须释放锁。
JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,两者实现细节不同
代码块 同步使用monitoenter 和 monitorexit 指令实现
方法 ACC_SYNCHRONIZED

1 对象头

synchronized用的锁是存在java对象头里的。数组对象对象头 3个字宽,非数组对象头 2个字宽

32位虚拟机 1字宽等于 4字节 32bit

Mark Word 存储接口 待完善

2 锁升级

Java SE 1.6 为了减少获得锁和释放锁带来的性能消耗,引入了 偏向锁 轻量级 锁。在1.6中锁一共有4种状态。 无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。这几种状态随着竞争情况逐渐升级。锁可以升级不可以降级。

2.1 偏向锁

大多数情况下情况,锁不存在多线程竞争,且总是由一个线程多次获取,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步代码块并获得锁时,会在对象头和栈帧中的锁记录中存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁解锁,只需测试一下Mark Word里是否存储着指向当前线程的偏向锁。

偏向锁

流程

当线程访问同步块并获取锁时流程如下:

  1. 检查mark word 的线程id
  2. 如果为空,则CAS替换为当前线程,替换成功则获取锁成功,失败则撤销偏向锁
  3. 如果不为空,则检查线程id是否为本线程,如果是则表示持当前线程持有锁,如果失败则撤销偏向锁。

一旦发生竞争,2、3部操作失败,则撤销偏向锁。

偏向锁的撤销

  1. 偏向锁的撤销必须等到全局安全点,无字节码指令执行

  2. 锁优化

    jvm对synchronized的优化

    自旋锁

    互斥同步进入阻塞状态开销很大。所以应该尽量避免。在很多情况下,共享数据锁定时间很短。自旋锁的思想是让一个线程在请求共享数据锁时执行自旋操作一段时间,如果在这段时间能获得锁,接可以避免进入阻塞状态。
    自旋锁虽然能避免线程进入阻塞状态而减少开销,但是它需要占用CPU时间,它只适用于共享数据锁定时间很短的情况。
    JDK1.6中引入了自适应自旋锁。 自适应意味着自旋次数不再固定,而是由当前锁上一次自旋次数和拥有者的状态决定。

    锁消除

    锁消除是指对于检测出不可能存在竞争的锁进行消除。

    锁粗化

    如果一系列的连续操作都对同一对象反复加锁和解锁,频繁的加锁操作会导致性能损耗。
    将锁定范围,扩大到整个一系列操作。

    轻量级锁

    JDK1.6引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态