synchronized 锁升级全流程

简介: 本文深入解析Java中synchronized的锁升级机制,基于JDK17版本详细介绍了从无锁、偏向锁、轻量级锁到重量级锁的完整升级流程。文章通过对象头MarkWord的结构分析、JVM源码解读和实战代码验证,全面阐述了不同锁状态的特点、适用场景和性能差异,并澄清了常见误区。同时提供了生产环境的最佳实践建议,包括锁粒度优化、JVM参数配置和死锁规避策略,帮助开发者深入理解并发编程核心机制,提升多线程程序性能。

在Java并发编程领域,synchronized无疑是最基础也最核心的同步工具。从JDK早期的“重量级锁”标签,到JDK1.6引入锁升级机制带来的性能飞跃,再到JDK15之后对偏向锁的默认禁用,synchronized的优化从未停止。本文将基于JDK17 LTS版本,从底层对象布局、JVM源码实现几个维度,100%准确地拆解synchronized的锁升级全流程,让你彻底搞懂无锁、偏向锁、轻量级锁、重量级锁的底层逻辑,既能夯实并发编程的核心基础,也能解决生产环境中的实际并发问题。

一、前置核心知识:Java对象布局与Mark Word

整个synchronized锁升级机制,完全是围绕Java对象头中的Mark Word实现的,想要彻底搞懂锁升级,必须先吃透Java对象的内存布局。

在64位JVM(当前生产环境主流)开启压缩指针(-XX:+UseCompressedOops,JDK17默认开启)的情况下,Java对象在堆内存中的布局分为三个部分:

  1. 对象头(Object Header):分为Mark Word(标记字段,8字节)、Class Pointer(类型指针,4字节,压缩后);如果是数组对象,额外增加4字节的数组长度。
  2. 实例数据(Instance Data):对象的成员变量(包括父类继承的),按照8字节对齐规则排列。
  3. 对齐填充(Padding):保证整个对象的大小是8字节的整数倍,满足JVM的内存对齐要求。

核心重点:Mark Word动态数据结构

Mark Word是一个8字节(64位)的动态数据结构,会根据对象的运行状态复用存储空间,不同状态下的位分布完全不同,这是锁升级的核心载体。以下是64位JVM开启压缩指针后,Mark Word在不同锁状态下的权威位分布(基于OpenJDK 17源码):

锁状态 64位Mark Word位分布(从高位到低位) 偏向锁标志(1位) 锁标志(2位)
无锁不可偏向 unused(25) | identityHashCode(31) | unused(1) | 分代年龄(4) 0 01
无锁可偏向 thread(54) | epoch(2) | unused(1) | 分代年龄(4) 1 01
偏向锁 偏向线程ID(54) | epoch(2) | unused(1) | 分代年龄(4) 1 01
轻量级锁 指向线程栈Lock Record的指针(62) 00
重量级锁 指向ObjectMonitor对象的指针(62) 10
GC标记 空(62) 11

这里必须明确两个核心判断位,也是很多技术文章的常见错误点:

  • lock位(2位):决定锁的基础状态,仅靠这一位无法区分无锁和偏向锁。
  • biased_lock位(1位):与lock位配合,01+0为无锁不可偏向,01+1为可偏向/偏向锁状态。

二、synchronized的使用场景与底层JVM实现

2.1 三种核心使用场景

synchronized的本质是对某个对象加锁,根据锁对象的不同,分为三种使用场景:

  1. 修饰实例方法:锁对象为当前实例this,属于对象锁,不同实例的锁互不影响。
  2. 修饰静态方法:锁对象为当前类的Class对象,属于类锁,对该类的所有实例生效。
  3. 修饰代码块:锁对象为括号内指定的对象,可自定义锁粒度,是生产环境推荐的用法。

2.2 底层JVM实现原理

根据JVM规范,synchronized的底层实现分为两种形式,本质都是获取对象对应的监视器锁:

  1. 同步代码块:通过monitorentermonitorexit字节码指令实现。线程执行到monitorenter时尝试获取对象的监视器所有权,执行到monitorexit时释放所有权,编译器会保证异常时也能执行monitorexit,避免死锁。
  2. 同步方法:通过方法修饰符中的ACC_SYNCHRONIZED标志实现。JVM调用方法时会检查该标志,若开启则自动尝试获取对象的监视器锁,方法执行完毕后自动释放,语义与monitorenter/monitorexit完全一致。

所有Java对象都天然关联一个ObjectMonitor监视器对象,这是重量级锁的核心载体,而偏向锁、轻量级锁不会直接初始化该对象,这也是不同锁类型的核心性能差异来源。

三、锁升级全流程核心原理

synchronized的锁升级是JVM为了减少同步开销而做的自适应优化,核心逻辑是:根据竞争激烈程度,从低开销锁逐步升级到高开销锁,锁的膨胀过程在持有期间是单向的,不可降级,锁完全释放后会重置为无锁状态。

锁升级全流程总览

3.1 第一阶段:无锁状态

无锁状态分为两种子状态,是锁升级的起点:

  1. 无锁可偏向状态:开启偏向锁的前提下,对象刚创建,未调用过System.identityHashCode()Mark Wordbiased_lock=1lock=01,线程ID、epoch字段全为0,可被第一个获取锁的线程偏向。
  2. 无锁不可偏向状态:关闭偏向锁,或对象已调用过identityHashCode()Mark Wordbiased_lock=0lock=01,存储了对象的哈希值,无法进入偏向锁状态,获取锁时直接进入轻量级锁流程。

核心知识点:为什么调用identityHashCode会禁用偏向锁?

64位JVM中,对象的identityHashCode是31位,正好占用了偏向锁状态下存储线程ID的54位空间的低31位。一旦写入哈希值,就没有足够空间存储偏向线程ID,JVM会强制禁用该对象的偏向锁,这是底层位空间冲突导致的必然结果,后续会通过代码实战验证该逻辑。

3.2 第二阶段:偏向锁

核心设计思想

在无多线程竞争的场景下,完全消除同步操作的开销,让锁永久偏向于第一个获取它的线程。后续该线程重入同步块时,无需任何CAS操作,仅需简单的字段检查,性能几乎与无锁代码一致。

JDK17特殊说明

根据JEP 374规范,JDK15及以上版本默认禁用偏向锁-XX:+UseBiasedLocking参数被标记为废弃,-XX:BiasedLockingStartupDelay默认值为0。若要开启偏向锁,需在JVM启动参数中添加:

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 --add-opens java.base/java.lang=ALL-UNNAMED

其中--add-opens参数用于JOL工具查看对象头,适配JDK17的模块化机制。

偏向锁的获取流程

  1. 线程进入同步块时,检查对象Mark Wordbiased_lock=1lock=01,确认处于可偏向状态。
  2. 检查Mark Word中的线程ID字段:
  • 若为当前线程ID,说明已持有偏向锁,直接进入同步块,无任何额外开销。
  • 若为空,说明无线程持有偏向锁,当前线程通过CAS操作将自身线程ID写入Mark Word
  1. CAS成功:当前线程获得偏向锁,后续重入无需任何同步操作。
  2. CAS失败:说明有其他线程已竞争该锁,触发偏向锁撤销流程。

偏向锁的撤销机制

偏向锁的撤销是较重的操作,需要等待全局安全点(STW),暂停所有持有该锁的线程,检查持有偏向锁的线程状态:

  1. 若线程已死亡,直接将Mark Word重置为无锁可偏向状态。
  2. 若线程仍存活,遍历该线程的栈帧,检查是否仍在使用该锁:
  • 仍在使用:将偏向锁升级为轻量级锁,把Displaced Mark Word写入线程的Lock RecordMark Word改为指向Lock Record的指针,lock位改为00。
  • 已不再使用:将Mark Word重置为无锁可偏向状态,完成撤销。

批量重偏向与批量撤销

为了避免大量对象的偏向锁频繁撤销导致的STW开销,JVM引入了批量优化机制:

  • 批量重偏向:当某个类的对象偏向锁撤销次数达到-XX:BiasedLockingBulkRebiasThreshold(默认20),JVM会给该类的epoch字段加1,所有该类对象的偏向锁需重新偏向,解决同类型对象在多线程间交替使用导致的频繁撤销问题。
  • 批量撤销:当某个类的对象偏向锁撤销次数达到-XX:BiasedLockingBulkRevokeThreshold(默认40),JVM会判定该类存在持续的多线程竞争,直接禁用该类所有对象的偏向锁,后续创建的该类对象均为不可偏向状态。

3.3 第三阶段:轻量级锁

核心设计思想

在多线程交替使用锁、无同时竞争的场景下,避免重量级锁的用户态与内核态切换开销,基于线程栈帧中的Lock Record和CAS操作实现,属于用户态自旋锁,开销远小于重量级锁。

轻量级锁的获取流程

  1. 线程进入同步块时,若对象处于无锁不可偏向状态,或偏向锁撤销后需升级,JVM会在当前线程的栈帧中创建Lock Record空间,用于存储对象当前Mark Word的拷贝,该拷贝称为Displaced Mark Word
  2. 线程通过CAS操作,尝试将对象的Mark Word替换为指向当前线程栈帧中Lock Record的指针。
  3. CAS成功:当前线程获得轻量级锁,将Mark Wordlock位设置为00,执行同步代码。
  4. CAS失败:说明其他线程已持有该对象的轻量级锁,当前线程进入自适应自旋阶段,不断重试CAS操作尝试获取锁。

自适应自旋机制

JDK17中的自旋锁是完全自适应的,JVM会根据前一次在同一个锁上的自旋时间、锁持有者的状态,动态调整自旋次数:

  • 若前一次自旋成功获取了锁,JVM会认为本次自旋大概率成功,增加自旋次数。
  • 若前一次自旋失败,JVM会减少自旋次数,甚至直接跳过自旋升级为重量级锁,避免CPU空转浪费资源。

轻量级锁的释放流程

  1. 线程执行完同步代码后,通过CAS操作,将Lock Record中存储的Displaced Mark Word写回对象的Mark Word
  2. CAS成功:锁释放完成,对象回到无锁状态。
  3. CAS失败:说明自旋过程中锁已被升级为重量级锁,需进入重量级锁的释放流程,唤醒等待队列中的线程。

适用场景

轻量级锁适用于多线程交替执行同步块、锁持有时间短、无同时竞争的场景。若多个线程同时竞争锁,轻量级锁的自旋会消耗大量CPU资源,性能反而会急剧下降,此时JVM会将锁升级为重量级锁。

3.4 第四阶段:重量级锁

核心设计思想

在多线程同时激烈竞争锁的场景下,通过操作系统的互斥量(mutex)实现同步,避免CPU空转,保证线程安全。

核心载体:ObjectMonitor

重量级锁的底层是ObjectMonitor对象,其核心结构来自OpenJDK 17源码:

  • _owner:指向当前持有锁的线程。
  • _EntryList:竞争锁失败的线程进入该队列,处于BLOCKED阻塞状态。
  • _WaitSet:调用了wait()方法的线程进入该队列,处于WAITING等待状态。
  • _count:锁的重入次数。
  • _recursions:锁的重入深度。

重量级锁的获取流程

  1. 当轻量级锁的自适应自旋多次失败,CAS仍未成功,JVM会将对象的锁升级为重量级锁,初始化ObjectMonitor对象,将对象的Mark Word设置为指向ObjectMonitor的指针,lock位改为10。
  2. 竞争锁的线程进入ObjectMonitor_EntryList队列,线程状态从RUNNABLE变为BLOCKED,操作系统将线程挂起,发生用户态到内核态的切换
  3. 持有锁的线程释放锁时,会从_EntryList中唤醒一个线程,重新竞争锁。
  4. 重入机制:持有锁的线程可多次重入,_count_recursions字段会记录重入次数,释放时需对应次数的monitorexit,直到_count为0,锁才会真正释放。

性能开销说明

重量级锁的核心开销来自用户态与内核态的切换,线程的阻塞和唤醒需要操作系统内核完成,涉及进程上下文的保存和恢复,开销极大。因此重量级锁适用于锁持有时间长、竞争激烈的场景,此时切换开销远小于CPU空转的开销。

四、锁升级全流程实战代码验证

以下所有代码均基于JDK17编写,用于验证上述锁升级的核心原理。

4.1 项目依赖配置(pom.xml)

采用所有组件的最新稳定版本,符合生产环境规范:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

   <modelVersion>4.0.0</modelVersion>

   <groupId>com.jam</groupId>
   <artifactId>synchronized-demo</artifactId>
   <version>1.0.0-SNAPSHOT</version>

   <properties>
       <maven.compiler.source>17</maven.compiler.source>
       <maven.compiler.target>17</maven.compiler.target>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <lombok.version>1.18.34</lombok.version>
       <jol.version>0.17</jol.version>
       <slf4j.version>2.0.12</slf4j.version>
       <logback.version>1.5.6</logback.version>
       <spring.version>6.1.15</spring.version>
       <fastjson2.version>2.0.52</fastjson2.version>
       <guava.version>33.1.0-jre</guava.version>
   </properties>

   <dependencies>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>${lombok.version}</version>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>org.openjdk.jol</groupId>
           <artifactId>jol-core</artifactId>
           <version>${jol.version}</version>
       </dependency>
       <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-api</artifactId>
           <version>${slf4j.version}</version>
       </dependency>
       <dependency>
           <groupId>ch.qos.logback</groupId>
           <artifactId>logback-classic</artifactId>
           <version>${logback.version}</version>
       </dependency>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-core</artifactId>
           <version>${spring.version}</version>
       </dependency>
       <dependency>
           <groupId>com.alibaba.fastjson2</groupId>
           <artifactId>fastjson2</artifactId>
           <version>${fastjson2.version}</version>
       </dependency>
       <dependency>
           <groupId>com.google.guava</groupId>
           <artifactId>guava</artifactId>
           <version>${guava.version}</version>
       </dependency>
   </dependencies>

   <build>
       <plugins>
           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-compiler-plugin</artifactId>
               <version>3.13.0</version>
               <configuration>
                   <source>17</source>
                   <target>17</target>
               </configuration>
           </plugin>
       </plugins>
   </build>
</project>

4.2 无锁状态与对象头验证

package com.jam.demo;

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import org.springframework.util.ObjectUtils;

/**
* 无锁状态对象头验证示例
*
* @author ken
* @date 2026-03-05
*/

@Slf4j
public class NoLockDemo {

   private static final Object LOCK_OBJECT = new Object();

   public static void main(String[] args) {
       log.info("===== 刚创建的对象,无锁状态 =====");
       printObjectHeader(LOCK_OBJECT);

       log.info("===== 调用identityHashCode()之后的对象头 =====");
       int identityHashCode = System.identityHashCode(LOCK_OBJECT);
       log.info("对象的identityHashCode: {}", Integer.toHexString(identityHashCode));
       printObjectHeader(LOCK_OBJECT);
   }

   /**
    * 打印对象头信息
    *
    * @param obj 待打印的对象
    */

   private static void printObjectHeader(Object obj) {
       if (ObjectUtils.isEmpty(obj)) {
           log.error("打印对象头失败,对象为空");
           return;
       }
       String classLayout = ClassLayout.parseInstance(obj).toPrintable();
       log.info("\n{}", classLayout);
   }
}

运行说明:需添加JVM参数--add-opens java.base/java.lang=ALL-UNNAMED,运行后可观察到:刚创建的对象Mark Word的偏向锁标志位,调用identityHashCode后,哈希值被写入Mark Word,偏向锁标志位变为0,对象进入不可偏向状态。

4.3 偏向锁状态验证

package com.jam.demo;

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import org.springframework.util.ObjectUtils;

/**
* 偏向锁状态验证示例
* 需添加JVM启动参数:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 --add-opens java.base/java.lang=ALL-UNNAMED
*
* @author ken
* @date 2026-03-05
*/

@Slf4j
public class BiasedLockDemo {

   private static final Object LOCK_OBJECT = new Object();

   public static void main(String[] args) throws InterruptedException {
       log.info("===== 刚创建的对象,可偏向无锁状态 =====");
       printObjectHeader(LOCK_OBJECT);

       log.info("===== 主线程第一次获取锁,偏向锁获取 =====");
       synchronized (LOCK_OBJECT) {
           printObjectHeader(LOCK_OBJECT);
       }

       log.info("===== 主线程释放锁之后,偏向锁状态保持 =====");
       printObjectHeader(LOCK_OBJECT);

       log.info("===== 主线程第二次重入锁,偏向锁重入 =====");
       synchronized (LOCK_OBJECT) {
           printObjectHeader(LOCK_OBJECT);
       }

       log.info("===== 新线程竞争锁,触发偏向锁撤销 =====");
       Thread thread = new Thread(() -> {
           synchronized (LOCK_OBJECT) {
               log.info("===== 新线程获取锁,锁升级为轻量级锁 =====");
               printObjectHeader(LOCK_OBJECT);
           }
       });
       thread.start();
       thread.join();

       log.info("===== 偏向锁撤销后,对象最终状态 =====");
       printObjectHeader(LOCK_OBJECT);
   }

   /**
    * 打印对象头信息
    *
    * @param obj 待打印的对象
    */

   private static void printObjectHeader(Object obj) {
       if (ObjectUtils.isEmpty(obj)) {
           log.error("打印对象头失败,对象为空");
           return;
       }
       String classLayout = ClassLayout.parseInstance(obj).toPrintable();
       log.info("\n{}", classLayout);
   }
}

运行结果:开启偏向锁后,主线程第一次获取锁时,Mark Word中写入主线程ID,进入偏向锁状态;释放锁后偏向锁状态保持,重入无额外开销;新线程竞争锁触发偏向锁撤销,锁升级为轻量级锁,lock位变为00。

4.4 轻量级锁状态验证

package com.jam.demo;

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import org.springframework.util.ObjectUtils;

/**
* 轻量级锁状态验证示例
* 需添加JVM启动参数:--add-opens java.base/java.lang=ALL-UNNAMED
*
* @author ken
* @date 2026-03-05
*/

@Slf4j
public class LightweightLockDemo {

   private static final Object LOCK_OBJECT = new Object();
   private static int count = 0;

   public static void main(String[] args) throws InterruptedException {
       log.info("===== 初始无锁状态 =====");
       printObjectHeader(LOCK_OBJECT);

       Thread thread1 = new Thread(() -> {
           for (int i = 0; i < 5; i++) {
               synchronized (LOCK_OBJECT) {
                   count++;
                   log.info("线程1执行同步代码,当前count: {}", count);
                   if (i == 2) {
                       log.info("===== 线程1持有锁,轻量级锁状态 =====");
                       printObjectHeader(LOCK_OBJECT);
                   }
               }
           }
       }, "thread-1");

       Thread thread2 = new Thread(() -> {
           for (int i = 0; i < 5; i++) {
               synchronized (LOCK_OBJECT) {
                   count++;
                   log.info("线程2执行同步代码,当前count: {}", count);
                   if (i == 2) {
                       log.info("===== 线程2持有锁,轻量级锁状态 =====");
                       printObjectHeader(LOCK_OBJECT);
                   }
               }
           }
       }, "thread-2");

       thread1.start();
       thread1.join();
       thread2.start();
       thread2.join();

       log.info("===== 所有线程执行完毕,最终count: {}, 锁状态 =====", count);
       printObjectHeader(LOCK_OBJECT);
   }

   /**
    * 打印对象头信息
    *
    * @param obj 待打印的对象
    */

   private static void printObjectHeader(Object obj) {
       if (ObjectUtils.isEmpty(obj)) {
           log.error("打印对象头失败,对象为空");
           return;
       }
       String classLayout = ClassLayout.parseInstance(obj).toPrintable();
       log.info("\n{}", classLayout);
   }
}

运行结果:两个线程交替执行同步代码,无同时竞争,锁保持轻量级锁状态,lock位为00,Mark Word中存储指向持有锁线程栈帧中Lock Record的指针。

4.5 重量级锁状态验证

package com.jam.demo;

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import org.springframework.util.ObjectUtils;

import java.util.concurrent.CountDownLatch;

/**
* 重量级锁状态验证示例
* 需添加JVM启动参数:--add-opens java.base/java.lang=ALL-UNNAMED
*
* @author ken
* @date 2026-03-05
*/

@Slf4j
public class HeavyweightLockDemo {

   private static final Object LOCK_OBJECT = new Object();
   private static int count = 0;
   private static final int THREAD_COUNT = 10;
   private static final int LOOP_COUNT = 1000;

   public static void main(String[] args) throws InterruptedException {
       CountDownLatch startLatch = new CountDownLatch(1);
       CountDownLatch endLatch = new CountDownLatch(THREAD_COUNT);

       log.info("===== 初始无锁状态 =====");
       printObjectHeader(LOCK_OBJECT);

       for (int i = 0; i < THREAD_COUNT; i++) {
           new Thread(() -> {
               try {
                   startLatch.await();
                   for (int j = 0; j < LOOP_COUNT; j++) {
                       synchronized (LOCK_OBJECT) {
                           count++;
                           if (count == 5000) {
                               log.info("===== 多线程激烈竞争,锁升级为重量级锁 =====");
                               printObjectHeader(LOCK_OBJECT);
                           }
                       }
                   }
               } catch (InterruptedException e) {
                   Thread.currentThread().interrupt();
                   log.error("线程执行异常", e);
               } finally {
                   endLatch.countDown();
               }
           }, "thread-" + i).start();
       }

       startLatch.countDown();
       endLatch.await();

       log.info("===== 所有线程执行完毕,最终count: {}, 锁最终状态 =====", count);
       printObjectHeader(LOCK_OBJECT);
   }

   /**
    * 打印对象头信息
    *
    * @param obj 待打印的对象
    */

   private static void printObjectHeader(Object obj) {
       if (ObjectUtils.isEmpty(obj)) {
           log.error("打印对象头失败,对象为空");
           return;
       }
       String classLayout = ClassLayout.parseInstance(obj).toPrintable();
       log.info("\n{}", classLayout);
   }
}

运行结果:10个线程同时竞争锁,自适应自旋快速失败,锁升级为重量级锁,lock位变为10,Mark Word中存储指向ObjectMonitor对象的指针,最终count值为10000,保证线程安全。

4.6 identityHashCode导致偏向锁失效验证

package com.jam.demo;

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import org.springframework.util.ObjectUtils;

/**
* 调用identityHashCode后偏向锁失效验证示例
* 需添加JVM启动参数:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 --add-opens java.base/java.lang=ALL-UNNAMED
*
* @author ken
* @date 2026-03-05
*/

@Slf4j
public class BiasedLockInvalidDemo {

   private static final Object LOCK_OBJECT_1 = new Object();
   private static final Object LOCK_OBJECT_2 = new Object();

   public static void main(String[] args) {
       log.info("===== 锁对象1:刚创建,可偏向状态 =====");
       printObjectHeader(LOCK_OBJECT_1);

       log.info("===== 锁对象1:获取锁,进入偏向锁状态 =====");
       synchronized (LOCK_OBJECT_1) {
           printObjectHeader(LOCK_OBJECT_1);
       }

       log.info("===== 锁对象2:刚创建,可偏向状态 =====");
       printObjectHeader(LOCK_OBJECT_2);

       log.info("===== 锁对象2:调用identityHashCode =====");
       int identityHashCode = System.identityHashCode(LOCK_OBJECT_2);
       log.info("锁对象2的identityHashCode: {}", Integer.toHexString(identityHashCode));
       printObjectHeader(LOCK_OBJECT_2);

       log.info("===== 锁对象2:获取锁,无法进入偏向锁,直接进入轻量级锁 =====");
       synchronized (LOCK_OBJECT_2) {
           printObjectHeader(LOCK_OBJECT_2);
       }
   }

   /**
    * 打印对象头信息
    *
    * @param obj 待打印的对象
    */

   private static void printObjectHeader(Object obj) {
       if (ObjectUtils.isEmpty(obj)) {
           log.error("打印对象头失败,对象为空");
           return;
       }
       String classLayout = ClassLayout.parseInstance(obj).toPrintable();
       log.info("\n{}", classLayout);
   }
}

运行结果:锁对象1正常进入偏向锁状态;锁对象2调用identityHashCode后,偏向锁标志位变为0,获取锁时直接进入轻量级锁状态,验证了位空间冲突导致偏向锁失效的核心原理。

五、核心细节与易混淆点辨析

5.1 锁升级是完全不可逆的吗?

这是最常见的认知错误。准确结论是:锁的膨胀过程在持有期间是单向的,只能从低级别升级到高级别,不可降级;但当锁完全释放后,对象的Mark Word会被重置为无锁状态,下一次竞争会重新开始锁升级流程。例如,一个对象的锁升级为重量级锁后,当所有线程都释放锁,对象会回到无锁状态,下一次线程获取锁时,会重新从偏向锁(开启时)或轻量级锁开始。

5.2 三种锁类型核心区别对比

锁类型 核心实现 适用场景 性能开销 线程安全保障
偏向锁 Mark Word存储偏向线程ID,无CAS操作 单线程长期使用锁,无多线程竞争 几乎为0,与无锁代码一致 无竞争下线程安全
轻量级锁 线程栈帧Lock Record + 自适应CAS自旋 多线程交替使用锁,持有时间短,无同时竞争 用户态CAS操作,开销小,自旋失败消耗CPU 交替竞争下线程安全
重量级锁 操作系统互斥量mutex + ObjectMonitor 多线程同时激烈竞争,锁持有时间长 用户态与内核态切换,开销极大 激烈竞争下线程安全

5.3 轻量级锁和自旋锁的关系?

轻量级锁获取失败后会采用自旋重试,但自旋锁不等于轻量级锁。自旋是一种获取锁的重试机制,不仅轻量级锁会使用,重量级锁在进入阻塞之前也会尝试自旋。轻量级锁的核心是基于Lock Record的CAS操作,自旋是其竞争失败后的优化手段。

5.4 对象锁和类锁的核心区别?

  • 对象锁:锁对象为实例对象,不同实例的对象锁互不影响,synchronized修饰实例方法、synchronized(this)均为对象锁。
  • 类锁:锁对象为类的Class对象,一个类仅有一个Class对象,因此类锁对该类的所有实例生效,synchronized修饰静态方法、synchronized(XXX.class)均为类锁。
  • 本质上,两种锁的底层锁升级机制完全一致,仅锁对象不同。

5.5 为什么wait/notify必须在synchronized同步块中调用?

这三个方法均基于ObjectMonitor实现:调用wait()会释放锁并将线程加入WaitSet队列,调用notify()会唤醒WaitSet中的线程,这些操作都必须先持有对象的监视器锁。若不在同步块中调用,JVM会抛出IllegalMonitorStateException异常,这是JVM规范的强制要求,也是为了保证线程间通信的原子性和线程安全。

5.6 JDK17与JDK8中synchronized的核心区别?

  1. 偏向锁:JDK8默认开启偏向锁,JDK17默认禁用偏向锁,相关参数被标记为废弃。
  2. 自旋优化:JDK17的自适应自旋算法更智能,能根据运行时情况动态调整自旋次数,减少CPU资源浪费。
  3. 内存屏障:JDK17对synchronized的内存屏障语义做了优化,更贴合JMM规范,多核CPU下性能表现更好。
  4. 偏向锁优化:JDK17优化了批量重偏向/撤销机制,减少了STW时间。

六、生产环境最佳实践

6.1 锁粒度最小化原则

在保证线程安全的前提下,尽量缩小锁的范围,仅对需要同步的共享变量操作加锁,不要对整个方法加锁,减少锁的持有时间,降低竞争概率,避免锁快速升级为重量级锁。

  • 反例:整个方法加synchronized,内部包含大量非线程安全的耗时IO操作。
  • 正例:仅对共享变量的修改操作加锁,耗时操作移出同步块。

6.2 避免在同步块中执行耗时操作

同步块中若包含IO操作、网络调用、数据库查询等耗时操作,会导致锁持有时间大幅增加,多线程竞争概率急剧上升,锁会快速升级为重量级锁,性能严重下降。若必须执行耗时操作,尽量将其移出同步块。

6.3 合理选择锁对象

  • 锁对象必须是private final修饰的自定义对象,保证锁的封装性,避免外部代码获取锁对象导致死锁。
  • 禁止用StringInteger等包装类型作为锁对象,常量池缓存会导致锁对象被复用,引发非预期的线程安全问题。
  • 禁止用this作为锁对象,外部代码可获取到该实例,存在死锁风险。

6.4 JVM参数优化建议

  • 高并发激烈竞争场景:保持JDK17默认配置,关闭偏向锁,避免偏向锁频繁撤销带来的STW开销。
  • 单线程低竞争场景:可开启偏向锁,减少同步开销,提升性能。
  • 自旋次数:不建议手动调整自旋次数,JDK17的自适应自旋已高度优化,手动调整反而可能导致性能下降。

6.5 利用JIT的锁优化机制

JIT编译器会对synchronized做两项核心优化,可通过合理编码提升优化效果:

  • 锁消除:JIT通过逃逸分析,发现对象不会逃逸出当前线程,会直接消除该对象的同步锁。尽量减少对象逃逸,提升锁消除概率。
  • 锁粗化:JIT发现相邻的多个同步块使用同一个锁对象,会将其合并为一个同步块,减少多次加锁解锁的开销。避免在循环中频繁加锁解锁,尽量将同步块放在循环外部。

6.6 死锁规避规范

  • 固定加锁顺序:所有线程按照统一的顺序获取锁,避免循环等待。
  • 避免锁嵌套:尽量不要在一个同步块中获取另一个锁,减少死锁概率。
  • 超时控制:若使用可重入锁,可设置tryLock超时时间,避免无限期等待。

七、高频面试题与标准答案

  1. 简述synchronized的锁升级全流程答:synchronized的锁升级是JVM为减少同步开销做的自适应优化,基于对象的Mark Word实现,流程如下:
  • 无锁状态:对象刚创建,未被任何线程锁定,分为可偏向和不可偏向两种子状态。
  • 偏向锁:开启偏向锁的前提下,第一个线程获取锁时,通过CAS将自身线程ID写入Mark Word,锁偏向于该线程,后续重入无需任何同步操作,开销极小。
  • 轻量级锁:出现多线程交替竞争锁时,偏向锁被撤销,升级为轻量级锁。线程在栈帧中创建Lock Record,通过CAS将对象的Mark Word替换为指向Lock Record的指针,获取成功则持有锁,失败则进入自适应自旋。
  • 重量级锁:自旋多次失败,多线程同时激烈竞争锁时,锁升级为重量级锁,底层基于操作系统互斥量实现,竞争失败的线程会被阻塞,发生用户态到内核态的切换,开销极大。 锁的膨胀过程在持有期间是单向的,不可降级,锁完全释放后会重置为无锁状态。
  1. synchronized和ReentrantLock的核心区别答:两者都是可重入的独占锁,核心区别如下:
  • 底层实现:synchronized是JVM层面的关键字,基于对象头和ObjectMonitor实现;ReentrantLock是JDK层面的API,基于AQS抽象队列同步器实现。
  • 灵活性:synchronized的加锁解锁是自动的,无法手动控制;ReentrantLock可手动控制加锁解锁,支持尝试加锁、超时加锁、可中断加锁,灵活性更高。
  • 功能特性:ReentrantLock支持公平锁和非公平锁(默认非公平),synchronized仅支持非公平锁;ReentrantLock支持多个条件变量Condition,synchronized仅支持一个wait/notify队列。
  • 性能:低竞争场景下,synchronized的偏向锁、轻量级锁性能更好;高竞争场景下,ReentrantLock性能更稳定,JDK17中两者性能差距已极小。
  • 异常处理:synchronized会在异常时自动释放锁,不会导致死锁;ReentrantLock必须在finally块中手动释放锁,否则会导致死锁。
  1. 为什么调用identityHashCode会导致对象无法进入偏向锁状态?答:64位JVM中,对象的identityHashCode是31位,正好占用了偏向锁状态下存储偏向线程ID的54位空间的低31位。一旦调用identityHashCodeMark Word中会写入31位哈希值,没有足够空间存储偏向线程ID,因此JVM会强制禁用该对象的偏向锁,后续该对象获取锁时会直接进入轻量级锁流程。
  2. 什么是锁消除和锁粗化?答:两者都是JIT编译器对synchronized的优化手段:
  • 锁消除:JIT通过逃逸分析,判断某个锁对象不会逃逸出当前线程,不会被其他线程访问,就会直接消除该同步锁,避免不必要的加锁解锁开销。
  • 锁粗化:JIT发现相邻的多个同步块使用同一个锁对象,会将这些同步块合并为一个大的同步块,减少多次加锁解锁的开销,避免循环中频繁加锁解锁导致的性能损耗。
  1. synchronized的可重入性是如何实现的?答:synchronized的可重入性基于底层的计数器实现:
  • 偏向锁/轻量级锁:通过线程栈帧中的Lock Record数量记录重入次数,每重入一次创建一个新的Lock Record,释放时依次移除,直到所有Lock Record都被移除,锁才真正释放。
  • 重量级锁:通过ObjectMonitor中的_count_recursions字段记录重入次数,线程每重入一次,计数器加1,释放时减1,直到计数器为0,锁才真正释放。

结尾

synchronized是Java并发编程的基石,锁升级机制是其性能优化的核心。只有彻底理解其底层实现原理,才能写出高效、安全的并发代码,从根源上规避生产环境中的并发问题。

目录
相关文章
|
5天前
|
人工智能 安全 API
CoPaw:5分钟部署你的 AI助理
源自阿里巴巴开源生态的个人 AI 助理——CoPaw。作为阿里倾力打造的开源力作,CoPaw 完美打通钉钉、飞书、Discord 等多平台对话通道,支持定时任务自动化。内置 PDF/Office 深度处理、新闻摘要等强大技能,更开放自定义扩展接口。坚持数据全程私有化部署,绝不上传云端,让每一位用户都能在大厂技术加持下,拥有安全、专属的智能助手。
|
8天前
|
人工智能 自然语言处理 JavaScript
2026年Windows+Ollama本地部署OpenClaw保姆级教程:本地AI Agent+阿里云上快速搭建
2026年OpenClaw凭借本地部署、私有化运行的特性,成为打造个人智能体的核心工具,而Ollama作为轻量级本地大模型管理工具,能让OpenClaw摆脱对云端大模型的依赖,实现**本地推理、数据不泄露、全流程私有化**的智能体验。本文基于Windows 11系统,从硬件环境准备、Ollama安装与模型定制、OpenClaw部署配置、技能扩展到常见问题排查,打造保姆级本地部署教程,同时补充阿里云OpenClaw(Clawdbot)快速部署步骤,兼顾本地私有化需求与云端7×24小时运行需求,文中所有代码命令均可直接复制执行,确保零基础用户也能快速搭建属于自己的本地智能体。
9406 76
|
6天前
|
人工智能 安全 JavaScript
阿里云上+本地部署OpenClaw(小龙虾)新手攻略:解锁10大必备Skills,零基础也能玩转AI助手
2026年,开源AI代理工具OpenClaw(昵称“小龙虾”)凭借“能实际做事”的核心优势,在GitHub斩获25万+星标,成为现象级AI工具。它最强大的魅力在于可扩展的Skills(技能包)系统——通过ClawHub插件市场的数百个技能,能让AI助手从简单聊天升级为处理办公、学习、日常事务的全能帮手。
4793 13
|
7天前
|
人工智能 自然语言处理 机器人
保姆级教程:Mac本地搭建OpenClaw及阿里云上1分钟部署OpenClaw+飞书集成实战指南
OpenClaw(曾用名Clawdbot、Moltbot)作为2026年最热门的开源个人AI助手平台,以“自然语言驱动自动化”为核心,支持对接飞书、Telegram等主流通讯工具,可替代人工完成文件操作、日历管理、邮件处理等重复性工作。其模块化架构适配多系统环境,既可以在Mac上本地化部署打造私人助手,也能通过阿里云实现7×24小时稳定运行,完美兼顾隐私性与便捷性。
4921 11
|
9天前
|
人工智能 JSON JavaScript
手把手教你用 OpenClaw + 飞书,打造专属 AI 机器人
手把手教你用 OpenClaw(v2026.2.22-2)+ 飞书,10分钟零代码搭建专属AI机器人!内置飞书插件,无需额外安装;支持Claude等主流模型,命令行一键配置。告别复杂开发,像聊同事一样自然对话。
5236 13
手把手教你用 OpenClaw + 飞书,打造专属 AI 机器人
|
8天前
|
人工智能 监控 机器人
2026年零门槛部署 OpenClaw(Clawdbot)接入A股数据,实现24小时股票分析保姆级教程
在AI赋能金融分析的浪潮中,OpenClaw(原Clawdbot/Moltbot)凭借开源灵活的架构,成为个人投资者打造专属智能分析助手的首选。通过接入A股实时数据,它能实现24小时市场监控、涨跌预警、潜力股推荐等核心功能,彻底解放人工盯盘的繁琐。而阿里云的稳定部署环境,更让这套系统实现全天候不间断运行,成为真正的“金融AI助手”。 本文基于OpenClaw v2026.1.25稳定版与QVeris免费A股数据接口,详细拆解阿里云OpenClaw部署步骤、A股数据接入流程、高级分析功能配置及多平台联动技巧,所有代码命令均可直接复制复用,即使无技术基础也能在1小时内完成从部署到实战的全流程。
3651 12
|
4天前
|
人工智能 JavaScript Ubuntu
5分钟上手龙虾AI!OpenClaw部署(阿里云+本地)+ 免费多模型配置保姆级教程(MiniMax、Claude、阿里云百炼)
OpenClaw(昵称“龙虾AI”)作为2026年热门的开源个人AI助手,由PSPDFKit创始人Peter Steinberger开发,核心优势在于“真正执行任务”——不仅能聊天互动,还能自动处理邮件、管理日程、订机票、写代码等,且所有数据本地处理,隐私完全可控。它支持接入MiniMax、Claude、GPT等多类大模型,兼容微信、Telegram、飞书等主流聊天工具,搭配100+可扩展技能,成为兼顾实用性与隐私性的AI工具首选。
2323 6