Java synchronized锁详解和升级过程 ,synchronized对象锁是JDK内置的一款对象锁,在我们使用synchronized关键字加锁时,实际上在JDK中锁的状态可能在无锁、偏向锁、轻量级锁、重量级锁中这几种状态变化。这几种锁的适用场景和触发条件也是各不相同。

1.Monitor对象头

Monitor对象头,在对象头的Markworld有很多信息,包括锁的状态。下面32位JVM中普通对象头结构

|--------------------------------------------------------------|
|                     Object Header (64 bits)                  |
|------------------------------------|-------------------------|
|        Mark Word (32 bits)         |    Klass Word (32 bits) |
|------------------------------------|-------------------------|

数组对象的对象头

|---------------------------------------------------------------------------------|
|                                 Object Header (96 bits)                         |
|--------------------------------|-----------------------|------------------------|
|        Mark Word(32bits)       |    Klass Word(32bits) |  array length(32bits)  |
|--------------------------------|-----------------------|------------------------|

其中Mark World部分内部结构如下:

|-------------------------------------------------------|--------------------|
|                  Mark Word (32 bits)                  |       State        |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|-------------------------------------------------------|--------------------|
|  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|-------------------------------------------------------|--------------------|
|               ptr_to_lock_record:30          | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
|               ptr_to_heavyweight_monitor:30  | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                              | lock:2 |    Marked for GC   |
|-------------------------------------------------------|--------------------|

biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁

biased_lock lock
001无锁
101偏向锁
000轻量级锁
010重量级锁
011GC标志

biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。
thread:持有偏向锁的线程ID。
epoch:偏向时间戳。
ptr_to_lock_record:指向栈中锁记录的指针。
ptr_to_heavyweight_monitor:指向管程Monitor的指针。

2.Monitor锁

每个Java对象都可以关联一个Monitor 对象,如果使用synchronized给对象.上锁(重量级)之后,该对象头的Mark Word中就被设置指向Monitor 对象的指针,下面是简单的图示:

  1. Thread-1、 Thread-2、 Thread-3三个线程执行同一个代码块,但是 Thread-2先执行性,这时候他就去找obj关联的Monitor对象,看看Owner是否为null,为null则代表这个对象没有人持有锁,然后将Monitor内的Owner设置为Thread-2,Thread-2就可以执行临界区中的代码。
  2. Thread-1、Thread-3后面才到,他们到达临界区之后也去查看obj对象关联的moniotr对象中的Owner,发现已经有Thread-2,那就得进入EntryList中等待Thread-2释放这个锁之后他们才有机会竞争这个锁。
  1. 刚开始Monitor中Owner为mull
  2. 当Thread-2执行synchronized(obj)就会将Monitor 的所有者Owner置为Thread-2, Monitor中只能有一个
  3. 在Thread-2上锁的过程中,如果Thread-3, Thread-4, Thread-5 也来执行synchronized(obj), 就会进入EntryList BLOCKED
  4. Thread-2 执行完同步代码块的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争的时是非公平的
  5. 图中WaitSet中的Thread-0, Thread-I是之前获得过锁,但条件不满足进入WAITING状态的线程。

3.轻量级锁

轻量级锁应用场景:如果一个对象虽然有多个线程来访问,但是多个线程的访问时间是错开的,没有产生竞争,这个时候就可以使用轻量级锁来优化。

轻量级锁对使用者是透明的,语法仍然是synchronized

假设下面两个同步代码块,用同一个对象加锁:

static final Object obj = new Object();
pubic static void method1(){
    //同步代码
    synchronized(obj){
    method2()
    }
}
public synchronized satic void method2(){
   synchronized(obj){
    //同步代码
    }
}

创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构, 内部可以存储锁定对象的Mark Word

让锁记录中Object reference指向锁对象,并尝试用cas替换Object的Mark Word,将Mark Word的值存入锁记录。

如果CAS(compare and swap)替换成功,对象头中存储了锁记录地址和状态00, 表示由该线程给对象加锁,这时图示如下:

如果CAS失败,则会有两种情况:

  1. 如果有他线程持有了该对象的锁,则表示有竞争,进入锁膨胀的过程
  2. 如果是自己执行了锁的重入,那么则再添加一条LockRecord作为重入的计数。

当退出synchronized 代码块(解锁时)如果有取值为null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减1

当退出synchronized代码块(解锁时)锁记录的值不为null, 这时使用cas将Mark Word的值恢复给对象头

  1. 成功,则解锁成功
  2. 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

4.锁膨胀

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

static final Object obj = new Object();
pubic static void method1(){
    //同步代码
    synchronized(obj){
    }
}

当Thread-l进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁

这时Thread-l加轻量级锁失败,进入锁膨胀流程 即为Object对象申请Monitor锁,让Object指向重量级锁地址然后自己进入Monitor的EntryList BLOCKED

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

5.自旋优化

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

自旋重试成功的情况:

自旋重试失败的情况:

6.偏向锁

轻量级锁在没有竞争时(就自己这个线程), 每次重入仍然需要执行CAS操作。

Java 6中引入了偏向锁来做进一步优化:只有第一次使用 CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以后只要不发生竞争,这个对象就归该线程所有。

static final Object obj = new Object();
pubic static void m1(){
    //同步代码
    synchronized(obj){
    method2()
    }
}
public synchronized satic void m2(){
   synchronized(obj){
    //同步代码
    m3();
    }

public synchronized satic void m3(){
   synchronized(obj){
    //同步代码
    }
}

偏向锁的状态

一个对象创建时:

  1. 如果开启了偏向锁(默认开启),那么对象创建后,markword值为0x05即最后3位为101, 这时它的hread、epoch、 age 都为0
  2. 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加VM参数-XX:BiasedLockingStartupDelay=0来禁用延迟
  3. 如果没有开启偏向锁,那么对象创建后,markword 值为0x01即最后3位为001,这时它的hashcode、age都为0,第一次用到hashcode时才会赋值。
  4. 当不同线程之间的竞争很激烈的时候就不适用偏向锁,可以添加JVM参数-XX: -UsleBiasedLocking 禁用偏向锁

偏向锁的撤销

下面几种情况会撤销:

  1. 调用对象的hashCode方法
  2. 有其他线程使用偏向锁对象时,偏向锁会升级成轻量级锁。

偏向锁批量重偏向

  1. 如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍有机会重新偏向T2,重偏向会重置对象的Thread ID 。
  2. 当撤销偏向锁阈值超过20次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程 。

偏向锁批量撤销

当撤销偏向锁阈值超过40次后,jvm会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的,新建类的对象也是不可偏向的。

整体过程

  1. 在没有禁用偏向锁的情况下,新对象默认创建时如果一个线程进入synchronized代码块,那么则是偏向锁,但是如果有不同的线程(非竞争)来访问这个代码块则会撤销偏向锁,尝试转为轻量级锁,如果存在竞争则会升级成重量级锁,如果这个时候还有别的线程前来竞争锁,后来的线程会先经行自旋操作,自旋过程中有可能会成功也可能会失败。

标签云

ajax AOP Bootstrap cdn Chevereto CSS Docker Editormd GC Github Hexo IDEA JavaScript jsDeliver JS樱花特效 JVM Linux Live2D markdown Maven MyBatis MyBatis-plus MySQL Navicat Oracle Pictures QQ Sakura SEO Spring Boot Spring Cloud Spring Cloud Alibaba SpringMVC Thymeleaf Vue Web WebSocket Wechat Social WordPress Yoast SEO 代理 分页 图床 小幸运 通信原理

Java synchronized锁详解和升级过程