当前位置: 首页 > news >正文

Java锁机制全解析:从AQS到CAS,深入理解synchronized与ReentrantLock

在Java并发编程中,锁是保证线程安全的核心机制。面试中,关于锁的底层实现、性能对比和适用场景的问题屡见不鲜。本文将系统解析Java中最重要的锁机制,包括AQS框架、CAS原理,以及synchronizedReentrantLock的核心区别,助你全面掌握锁相关知识点。

一、并发安全的基石:锁的核心作用

在多线程环境下,多个线程同时操作共享资源可能导致数据不一致(如i++的经典问题)。锁的核心作用是通过控制线程对共享资源的访问权限,保证同一时间只有一个(或有限个)线程能操作资源,从而避免并发安全问题。

Java中的锁机制可分为两类:

  • 隐式锁:如synchronized,由JVM自动管理,无需手动释放。
  • 显式锁:如ReentrantLock,需手动获取和释放,灵活性更高。

无论哪种锁,其底层都依赖两种核心技术:AQS框架CAS操作

二、深入理解AQS:Java锁的基础框架

AQS(AbstractQueuedSynchronizer,抽象队列同步器)是Java并发包中许多锁和同步工具的底层基础,如ReentrantLockCountDownLatchSemaphore等都基于AQS实现。

1. AQS的核心设计

AQS的核心思想是**“状态管理+队列等待”**,主要包含三个部分:

  • 状态变量(state):一个volatile int变量,用于表示同步状态(如锁的持有计数)。

    • 例如,ReentrantLock中,state=0表示锁未被持有,state>0表示被持有(数值代表重入次数)。
  • 双向等待队列(CLH队列):当线程获取锁失败时,会被包装成节点(Node) 加入队列尾部,进入阻塞状态等待唤醒。

    • 队列采用双向链表实现,每个节点包含线程引用、等待状态(如CANCELLEDSIGNAL等)。
  • 条件队列(Condition Queue):通过Condition接口实现,用于线程间的条件等待(类似Object.wait())。

    • 一个AQS可以关联多个条件队列,实现更灵活的等待/唤醒机制。

2. AQS的核心操作

AQS对外提供了模板方法,子类需重写以下核心方法(通过修改state实现自定义同步逻辑):

  • tryAcquire(int arg):尝试获取锁(独占模式)。
  • tryRelease(int arg):尝试释放锁(独占模式)。
  • tryAcquireShared(int arg):尝试获取共享锁(如Semaphore)。
  • tryReleaseShared(int arg):尝试释放共享锁。

工作流程(以独占锁为例):

  1. 线程调用acquire()方法获取锁,内部先调用tryAcquire()
  2. tryAcquire()成功(state修改成功),则直接获取锁。
  3. 若失败,当前线程被包装成Node加入等待队列,进入阻塞状态。
  4. 当锁释放时(tryRelease()成功),唤醒队列中的后继节点,使其重新尝试获取锁。

三、CAS:无锁编程的核心技术

CAS(Compare And Swap,比较并交换)是一种无锁原子操作,是实现乐观锁的基础,也是AQS、AtomicInteger等并发工具的底层依赖。

1. CAS的工作原理

CAS操作包含三个参数:内存地址(V)、预期值(A)、新值(B)

  • 核心逻辑:若内存地址V中的值等于预期值A,则将其更新为B;否则不做操作。
  • 操作是原子性的,由CPU指令(如cmpxchg)保证,无需加锁。

用伪代码表示:

boolean cas(V, A, B) {if (V的值 == A) {V = B;return true; // 操作成功}return false; // 操作失败
}

2. CAS在Java中的应用

Java通过sun.misc.Unsafe类提供CAS操作的封装,例如AtomicIntegerincrementAndGet()方法:

public final int incrementAndGet() {// 循环尝试CAS,直到成功return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}// Unsafe类中的CAS实现
public final int getAndAddInt(Object o, long offset, int delta) {int v;do {v = getIntVolatile(o, offset); // 获取当前值(保证可见性)} while (!compareAndSwapInt(o, offset, v, v + delta)); // CAS尝试更新return v;
}

3. CAS的优缺点

  • 优点:无锁操作,避免线程切换和阻塞的开销,在低并发场景下性能优于锁。
  • 缺点
    • ABA问题:若值从A变为B再变回A,CAS会误认为未修改(可通过版本号解决,如AtomicStampedReference)。
    • 自旋开销:高并发下CAS可能多次失败重试,导致CPU空转。
    • 只能保证单个变量的原子性:无法直接实现多变量的原子操作。

四、synchronized:JVM内置锁的进化之路

synchronized是Java最基本的同步机制,从JDK 1.0就存在,经过多年优化(如JDK 6的锁升级),性能已大幅提升。

1. synchronized的用法

  • 修饰方法:锁是当前对象实例(非静态方法)或类对象(静态方法)。
  • 修饰代码块:锁是()中的对象(可自定义锁对象)。
// 修饰非静态方法(锁为this)
public synchronized void method1() { ... }// 修饰静态方法(锁为类对象)
public static synchronized void method2() { ... }// 修饰代码块(锁为obj)
public void method3() {synchronized (obj) { ... }
}

2. 底层实现:从对象头到monitor

synchronized的实现依赖对象头monitor(监视器锁)

  • 对象头:Java对象在内存中的布局包含对象头,其中Mark Word字段存储锁状态(如无锁、偏向锁、轻量级锁、重量级锁)。
  • monitor:一种由C++实现的内核级锁(ObjectMonitor),包含等待队列、持有线程等信息。

JDK 6之前synchronized直接使用monitor,属于重量级锁,会导致线程从用户态切换到内核态,性能开销大。

3. JDK 6的锁升级优化

为提升性能,JDK 6引入了锁升级机制,让synchronized从无锁状态逐步升级为重量级锁,避免一开始就使用昂贵的monitor:

  1. 无锁状态:对象刚创建,未被任何线程锁定。
  2. 偏向锁:若只有一个线程获取锁,会在对象头记录线程ID,后续该线程可直接获取锁(无需CAS),减少无竞争场景的开销。
  3. 轻量级锁:当有第二个线程竞争锁时,偏向锁升级为轻量级锁,通过CAS竞争锁(线程不会阻塞,而是自旋尝试)。
  4. 重量级锁:当竞争激烈(自旋失败),轻量级锁升级为重量级锁,依赖monitor实现,未获取锁的线程进入阻塞状态。

锁升级流程:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(不可逆,只能升级不能降级)。

五、ReentrantLock:灵活的显式锁

ReentrantLock是JDK 1.5引入的显式锁,实现了Lock接口,功能与synchronized类似(均为可重入锁),但提供更高的灵活性。

1. ReentrantLock的核心特性

  • 可重入性:同一线程可多次获取锁(与synchronized一致),state计数递增,释放时递减至0才完全释放。
  • 公平锁与非公平锁
    • 非公平锁(默认):线程获取锁时直接尝试CAS,失败再入队列,可能导致线程饥饿,但性能更高。
    • 公平锁:线程按等待队列顺序获取锁,需通过new ReentrantLock(true)创建,性能较低但更公平。
  • 可中断锁:支持lockInterruptibly()方法,允许线程在等待锁时响应中断(synchronized不可中断)。
  • 超时获取锁:支持tryLock(long time, TimeUnit unit),超时未获取锁则返回false,避免无限等待。
  • 条件变量:通过newCondition()获取Condition对象,实现多条件等待(synchronized仅能通过对象本身的wait()/notify())。

2. 基本用法

Lock lock = new ReentrantLock(); // 非公平锁
// Lock lock = new ReentrantLock(true); // 公平锁try {lock.lock(); // 获取锁// 临界区代码
} finally {lock.unlock(); // 释放锁(必须在finally中,避免死锁)
}

3. 底层实现:基于AQS

ReentrantLock通过内部类Sync(继承AQS)实现,Sync有两个子类:

  • NonfairSync:非公平锁实现,tryAcquire()直接CAS竞争锁。
  • FairSync:公平锁实现,tryAcquire()会先检查队列是否有前驱节点,有则排队。

六、synchronized与ReentrantLock的核心区别

特性synchronizedReentrantLock
锁类型隐式锁(JVM自动管理)显式锁(需手动lock()/unlock())
可重入性支持支持
公平性非公平锁可选择公平/非公平锁
可中断性不可中断可中断(lockInterruptibly())
超时获取不支持支持(tryLock(long))
条件变量仅一个(通过wait()/notify())多个(通过Condition)
性能JDK 6+优化后与ReentrantLock接近高并发下略优(非公平锁)
底层实现基于对象头+monitor,锁升级机制基于AQS框架,CAS+等待队列
适用场景简单同步场景,代码简洁复杂同步场景(如超时、中断、多条件)

七、面试高频问题解析

1. 为什么说synchronized是可重入锁?

可重入锁指同一线程可多次获取同一把锁。synchronized的可重入性体现在:

  • 线程获取锁后,Mark Word记录线程ID,再次进入同步代码时无需重新竞争,只需增加锁计数器。
  • 例如,一个同步方法调用另一个同步方法(同一对象锁),不会产生死锁。

2. AQS的等待队列和条件队列有什么区别?

  • 等待队列:AQS的核心队列,存储获取锁失败的线程,用于实现锁的竞争与唤醒。
  • 条件队列:由Condition维护,存储调用await()的线程,需通过signal()唤醒后进入等待队列重新竞争锁。
  • 一个AQS只有一个等待队列,但可以有多个条件队列。

3. CAS的ABA问题如何解决?

  • 解决方案:给变量增加版本号,每次修改时版本号递增,CAS时同时检查值和版本号。
  • Java中AtomicStampedReference类通过"值+时间戳(版本号)"解决ABA问题。

4. 锁升级机制中,轻量级锁为什么要自旋?

轻量级锁的自旋是为了避免线程阻塞(阻塞/唤醒的开销远大于自旋)。当线程竞争不激烈时,自旋几次可能就获取到锁,无需进入阻塞状态。但自旋次数有限(JVM默认10次),避免CPU空耗。

5. 什么时候用synchronized,什么时候用ReentrantLock?

  • 优先用synchronized:简单场景(如单条件同步),代码简洁,JVM自动管理,不易出错。
  • ReentrantLock:需要公平锁、超时控制、中断功能或多条件等待的场景(如生产者-消费者模型多条件)。

总结

Java锁机制是并发编程的核心,从底层的CAS操作到AQS框架,再到synchronizedReentrantLock的具体实现,每一层都有其设计智慧:

  • CAS是无锁编程的基础,适合低并发场景。
  • AQS是锁的通用框架,通过状态管理和队列实现同步逻辑。
  • synchronized是JVM内置锁,经过优化后性能优异,适合简单场景。
  • ReentrantLock是灵活的显式锁,适合复杂同步需求。

文章转载自:

http://T28pw5QS.nrrzw.cn
http://3r7qYc2x.nrrzw.cn
http://DccIrOIZ.nrrzw.cn
http://oY8PDce0.nrrzw.cn
http://Zaf6uWlp.nrrzw.cn
http://BUBRFZgl.nrrzw.cn
http://5caGu1VS.nrrzw.cn
http://3c5JiV5A.nrrzw.cn
http://mc4hmKsK.nrrzw.cn
http://uLn0PvPk.nrrzw.cn
http://6R3rtTUt.nrrzw.cn
http://dqXBafI0.nrrzw.cn
http://ov0aGsjb.nrrzw.cn
http://HSWLzDSa.nrrzw.cn
http://dZKFR1xu.nrrzw.cn
http://klGpfG5l.nrrzw.cn
http://IQALPnHQ.nrrzw.cn
http://ysyrAGUW.nrrzw.cn
http://yZXIoEg7.nrrzw.cn
http://YxPeUauB.nrrzw.cn
http://EGEdX20t.nrrzw.cn
http://04dveCnY.nrrzw.cn
http://IXz68PUf.nrrzw.cn
http://TQ0Hv5RB.nrrzw.cn
http://58BjkXbk.nrrzw.cn
http://RsqG8ydN.nrrzw.cn
http://J5hOhe46.nrrzw.cn
http://eru7JPYH.nrrzw.cn
http://Iqzym6iw.nrrzw.cn
http://fKyui284.nrrzw.cn
http://www.dtcms.com/a/376046.html

相关文章:

  • 基于SpringBoot的天气预报系统的设计与实现
  • Android 14 servicemanager的前世今生
  • TC_Motion多轴运动-电子齿轮
  • webrtc弱网-DelayBasedBwe 类源码分析与算法原理
  • 【Floor报错注入】
  • Docker生产部署
  • 小型语言模型:智能体AI的未来?
  • js垃圾回收机制
  • STM32开发(USART总线:UART总线)
  • Typescript - 通俗易懂的 interface 接口,创建接口 / 基础使用 / 可选属性 / 只读属性 / 任意属性(详细教程)
  • FastGPT源码解析 Agent 智能体应用创建流程和代码分析
  • [网络入侵AI检测] 模型性能评估与报告
  • chmod与chown命令的深度解析
  • 7层的API网关
  • 链表问题:LeetCode 两数相加 - 算法解析与详解
  • 类型别名(type)与接口(interface)的抉择
  • 4.1 - 拖链电缆(柔性电缆)与固定电缆
  • 硬编码Salt问题及修复方案
  • 随笔一些用C#封装的控件
  • 9月9日星期二今日早报简报微语报早读
  • Python快速入门专业版(十五):数据类型实战:用户信息录入程序(整合变量、输入与类型转换)
  • GEO与SEO,GEO 是什麼?SEO + AI = GEO 生成式搜尋引擎優化 全解析
  • Asp .Net Core 系列:Asp .Net Core 集成 Hangfire+MySQL
  • 如果服务端有数据更新,浏览器缓存同时也没有过期,如何直接使用最新的数据
  • 使用java编写一个基础的彩票抽奖程序
  • 算法题 Day5---String类
  • 【靶场练习】--DVWA第二关Command Injection(命令执行)全难度分析
  • 什么是Adobe Analytics?数据驱动营销的关键工具​
  • 使用Docker搭建MaxKB智能体平台
  • 【链表】3.重排链表(medium)