文章目录

  • 1、轻量级锁
  • 1.1、加锁
  • 1.2、解锁
  • 2、重量级锁
  • 2.1、加锁和解锁
  • 2.2、自旋优化


1、轻量级锁

1.1、加锁

  • 使用场景:如果一个对象虽然有多个线程访问,但是多线程访问时间是错开的,即多线程之间不会发生竞争。
  • 轻量级锁对使用者是透明的,即语法仍然是synchronized

如下代码,2个同步方法,利用同一个对象加锁:

static final Object obj = new Object();
public static void method1() {
	synchronized(obj) {
		// 同步方法A
	}
}
public static void method2() {
	synchronized(obj) {
		// 同步方法B
	}
}

轻量级锁加锁过程解析:

  • 线程和对象的初始状态,图示:
  • java 重量级锁有哪些 java轻量级锁与重量级锁_synchronized

  • 创建锁记录(Lock Record)对象,每一个线程的栈帧都会包含一个锁记录的结构,内部用来存储锁对象的Mark Word 以及锁对象的地址。
  • 当线程Thread-0执行到synchronized同步代码块的时,把锁记录中Object Reference(指针)执行锁对象,并尝试用cas替换Object 的MarkWord,把MarkWord的值存入锁记录,图示:
  • cas简单理解为原子操作,执行过程中不会被打断
  • 轻量级锁MardWord结构:ptr_to_lock_record:30 00
  • 如果cas替换成功,对象头中存储了锁记录地址和状态 00,表示由该线程加锁,图示:
  • java 重量级锁有哪些 java轻量级锁与重量级锁_java_02

  • cas替换失败有两种情况
  • 如果有其他线程早于当前线程成功加锁,即Object MarkWord后2位一置为00,此时发生锁膨胀,升级为重量级锁
  • 如果是自己执行了synchronized锁重入,那么在添加一条Lock Record做为重入的计数,图示:

1.2、解锁

解锁过程分析:

  • 当线程退出syncronized代码块时,如果有取值为null的锁记录,表示由重入,这时重置锁记录,表示重入此时减一
  • 当线程退出syncronized代码块时,如果锁记录的值不为null,这时使用cas把MarkWord的值恢复为最初的值(即保存在锁记录中的值)
    • 解锁成功
    • 解锁失败,说明轻量级锁进行了锁膨胀或者已经升级为重量级锁,进入重量级锁解锁过程。
2、重量级锁

2.1、加锁和解锁

如果在尝试加轻量级锁的过程中,CAS操作无法成功,其中一种情况就是有其他线程给该对象已经加上轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

  • 当Thread-1进行轻量级锁加锁时,Thread-0已经给该对象加了轻量级锁。

  • 这时Thread-1加轻量级锁失败,进入锁膨胀流程

    • 即为Object 对象申请Monitor锁,让Object指向Monitor地址
    • 然后自己进入Monitor的EntryList Blocked
    • 图示,
    • java 重量级锁有哪些 java轻量级锁与重量级锁_加锁_03


  • 解锁:当Thread-0退出同步块解锁时,使用cas将MarkWord的值恢复给对象头,失败。这时,会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owner值为null,唤醒EntryList中阻塞的线程。

2.2、自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即持锁线程已经退出同步块,释放了锁),这是当前线程就可以避免阻塞。

自旋成功的情况

线程1(cup 1)

对象Mark

线程2(cup 2)

访问同步块,获取monitor

10(重量级锁)

-

成功(加锁)

10(重量级锁)

-

执行同步块

10(重量级锁)

访问同步块,获取monitor

退出同步块(解锁)

01(无锁)

自旋重试

-

10(重量级锁)

成功加锁

-

10(重量级锁)

执行同步块

-

01(无锁)

退出同步块(解锁)




自旋失败的情况

线程1(cup 1)

对象Mark

线程2(cup 2)

访问同步块,获取monitor

10(重量级锁)

-

成功(加锁)

10(重量级锁)

-

执行同步块

10(重量级锁)

访问同步块,获取monitor

执行同步块

10(重量级锁)

自旋重试

执行同步块

10(重量级锁)

自旋重试

执行同步块

10(重量级锁)

自旋重试

执行同步块

10(重量级锁)

阻塞




  • Java6之后自旋锁是自适应,比如对象刚刚一次自旋操作成功过,那么这次自旋成功的可能性会高,就多自旋几次;反之,就减少自旋次数。
  • 自旋会占用CPU时间,单核CPU自旋就是浪费,多核CPU自旋才能发挥优势。
  • Java 7之后不能控制是否开启自旋功能。