Java 中 synchronized 是如何工作的

Java synchronized keyword 实现原理 中提到和 synchronized 关键字相关的锁有

各自适用的场景:

  • 偏向锁,只有一个线程,无竞争环境
  • 轻量级锁,多个线程交替进入,自旋加等待,无需上下文切换,轻微竞争环境
  • 重量级锁,多个线程同时进入

偏向锁和轻量级锁都是 JVM 优化锁而引入的方法,目的是降低线程同步的开销。

一个对象在从无锁到重量级锁的过程如下。

线程访问对象:

  • 检查对象头 Mark Word 的线程 ID
  • 如果为空,在设置 CAS 替换为当前线程 ID,替换成功则获取成功,如果失败则撤销偏向锁
  • 不为空,检查是否为当前线程,如果是则获取锁成功,失败则撤销偏向锁

升级为轻量级锁

轻量级锁不需要申请互斥量,仅仅将 Mark Word 中部分字段更新指向线程栈中的 Lock Record,如果更新成功就获取轻量级锁成功。记录锁状态为轻量级锁;否则,说明已经有其他线程获得了轻量级锁,升级为重量级锁。

在多个线程竞争时:

  • JVM 在当前线程栈帧中创建 Lock Record,并将对象头中的 Mark Word复制到 Lock Record 中

  • 线程尝试使用 CAS 将对象头的 Mark Word 替换为指向 Lock Record 的指针。如果成功则获取锁,失败则先检查对象 Mark Word 是否指向当前线程栈帧,是,则获取锁,否则说明其他线程竞争锁,则膨胀为重量级锁。

  • 偏斜锁,一个线程执行同步资源时,自动获取资源。指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

  • 轻量锁,多个线程竞争同步资源时,没有获取资源的线程自旋等待锁释放,当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

  • 重量锁,多个线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒。指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。