【Java并发编程】锁机制:Lock体系:ReentrantLock、ReentrantReadWriteLock、Lock vs synchronized 区别(附《思维导图》+《面试高频考点清单》)

简介: 本文系统梳理Java Lock体系核心知识:涵盖ReentrantLock(可重入、公平/非公平、AQS实现)、ReentrantReadWriteLock(读写分离、锁降级、state拆分)及StampedLock(乐观读、缓解写饥饿),深度对比synchronized与Lock在实现、特性、性能及场景上的八大区别,助力高并发编程与面试通关。

思维导图

Java并发编程:Lock体系 系统性知识总结

一、Lock体系整体概述

1.1 产生背景

synchronized是Java内置的锁机制,但存在以下局限性:

  • 不可中断:获取锁的线程会一直阻塞,无法响应中断
  • 非公平:默认非公平,无法实现公平锁
  • 单一条件:只能关联一个条件变量
  • 无法尝试获取锁:不能设置超时时间,获取失败会无限阻塞

1.2 Lock接口定义

java.util.concurrent.locks.Lock是所有锁的顶级接口,定义了锁的基本操作:

public interface Lock {
   
    void lock(); // 阻塞获取锁
    void lockInterruptibly() throws InterruptedException; // 可中断获取锁
    boolean tryLock(); // 非阻塞尝试获取锁,立即返回
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 超时尝试获取锁
    void unlock(); // 释放锁
    Condition newCondition(); // 创建条件变量
}

1.3 Lock体系核心类图

Lock
├─ ReentrantLock (可重入锁)
├─ ReadWriteLock (读写锁接口)
│  └─ ReentrantReadWriteLock (可重入读写锁)
└─ StampedLock (JDK8新增,乐观读写锁)

二、ReentrantLock详解

2.1 基本特性

  • 可重入性:同一个线程可以多次获取同一把锁,内部维护了一个计数器
  • 支持公平/非公平模式:构造方法可指定,默认非公平
  • 可中断:支持lockInterruptibly()方法
  • 超时获取:支持tryLock(long, TimeUnit)方法
  • 多条件变量:支持创建多个Condition对象

2.2 实现原理:基于AQS

ReentrantLock内部通过AbstractQueuedSynchronizer(AQS)实现,AQS是Java并发包的基础框架:

  • 同步状态private volatile int state,0表示未锁定,>0表示已锁定
  • CLH队列:双向链表结构,用于存放等待锁的线程
  • 独占模式:ReentrantLock使用AQS的独占模式

2.3 公平锁 vs 非公平锁

特性 公平锁 非公平锁
获取顺序 严格按照线程等待顺序 先尝试直接获取锁,失败再排队
性能 较低(上下文切换多) 较高(减少上下文切换)
饥饿问题 可能出现(线程一直抢不到锁)
实现方式 new ReentrantLock(true) new ReentrantLock(false)(默认)

非公平锁获取流程

  1. 尝试CAS将state从0改为1,成功则直接获取锁
  2. 失败则调用acquire(1)进入AQS队列
  3. 队列中的线程按顺序获取锁

公平锁获取流程

  1. 直接调用acquire(1)
  2. 先检查队列是否有等待线程,有则排队
  3. 没有等待线程才尝试CAS获取锁

2.4 可重入性实现

当线程再次获取锁时:

  1. 检查当前线程是否是持有锁的线程
  2. 如果是,将state加1
  3. 释放锁时,state减1,直到state为0才真正释放锁

2.5 Condition条件变量

  • 替代了Object的wait()/notify()/notifyAll()方法
  • 一个Lock可以创建多个Condition,实现更精细的线程通信
  • 常用方法:await()signal()signalAll()

使用示例

Lock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();

// 生产者
lock.lock();
try {
   
    while (queue.size() == capacity) {
   
        notFull.await(); // 队列满,等待
    }
    queue.add(item);
    notEmpty.signal(); // 唤醒消费者
} finally {
   
    lock.unlock();
}

// 消费者
lock.lock();
try {
   
    while (queue.isEmpty()) {
   
        notEmpty.await(); // 队列空,等待
    }
    Object item = queue.remove();
    notFull.signal(); // 唤醒生产者
    return item;
} finally {
   
    lock.unlock();
}

三、ReentrantReadWriteLock详解

3.1 设计思想

  • 读写分离:读操作可以并发执行,写操作必须独占执行
  • 适用于读多写少的场景,能显著提高并发性能
  • 包含两个锁:读锁(共享锁)和写锁(独占锁)

3.2 基本特性

  • 可重入性:读锁和写锁都支持可重入
  • 锁降级:写锁可以降级为读锁,但读锁不能升级为写锁
  • 公平/非公平模式:构造方法可指定,默认非公平
  • 写锁排他:写锁与任何锁都互斥(读锁、写锁)
  • 读锁共享:多个线程可以同时持有读锁

3.3 实现原理

ReentrantReadWriteLock内部也基于AQS实现,将32位的state变量拆分为两部分:

  • 高16位:表示读锁的持有次数(共享锁计数)
  • 低16位:表示写锁的持有次数(独占锁计数)
state = 0x00000000
高16位:读锁计数  低16位:写锁计数

3.4 锁降级机制

定义:持有写锁的线程,可以先获取读锁,再释放写锁,从而将写锁降级为读锁。

目的:保证数据的可见性,避免其他线程在写操作完成后、读操作开始前修改数据。

正确示例

rwl.writeLock().lock();
try {
   
    // 执行写操作
    data = updateData();

    // 获取读锁(锁降级)
    rwl.readLock().lock();
} finally {
   
    rwl.writeLock().unlock(); // 释放写锁,此时持有读锁
}

try {
   
    // 执行读操作
    processData(data);
} finally {
   
    rwl.readLock().unlock(); // 释放读锁
}

注意:不支持锁升级(持有读锁时获取写锁会导致死锁)。

3.5 使用场景与注意事项

  • 适用场景:读多写少的缓存系统、配置管理、数据查询服务
  • 注意事项
    1. 读锁持有期间不能获取写锁(会导致死锁)
    2. 写锁持有期间可以获取读锁(锁降级)
    3. 读锁和写锁都必须在finally块中释放
    4. 写线程过多时可能导致读线程饥饿(可使用公平模式缓解)

四、Lock vs synchronized 全面对比

4.1 核心特性对比

特性 synchronized Lock
实现方式 JVM内置,字节码层面实现 JDK层面实现,纯Java代码
锁释放 自动释放(代码块结束或异常) 手动释放(必须在finally中调用unlock())
可重入性 支持 支持(ReentrantLock)
公平性 不支持(默认非公平) 支持(可指定公平/非公平)
可中断性 不支持 支持(lockInterruptibly())
超时获取 不支持 支持(tryLock(long, TimeUnit))
条件变量 只能关联一个(Object的wait/notify) 支持多个(newCondition())
锁类型 独占锁 支持独占锁、共享锁(读写锁)
性能 低并发下性能较好 高并发下性能更优

4.2 实现原理对比

synchronized实现原理

  • 基于对象头的Mark Word和监视器锁(Monitor)
  • 锁升级过程:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
  • 重量级锁依赖操作系统的互斥量(Mutex)实现

Lock实现原理

  • 基于AQS框架和CAS操作
  • 内部维护一个CLH队列存放等待线程
  • 不依赖操作系统,纯Java实现,避免了用户态与内核态的切换

4.3 使用场景对比

优先使用synchronized的场景

  • 简单的同步场景,代码量少
  • 不需要Lock的高级特性(可中断、超时、多条件)
  • 低并发环境,synchronized的性能已经足够
  • 对代码简洁性要求较高

优先使用Lock的场景

  • 需要公平锁的场景
  • 需要可中断获取锁的场景
  • 需要超时获取锁的场景
  • 需要多个条件变量的场景
  • 读多写少的场景(使用ReentrantReadWriteLock)
  • 高并发环境,需要更好的性能

五、最佳实践与常见陷阱

5.1 Lock使用最佳实践

  1. 必须在finally块中释放锁:防止异常导致锁无法释放

    Lock lock = new ReentrantLock();
    lock.lock();
    try {
         
        // 业务逻辑
    } finally {
         
        lock.unlock();
    }
    
  2. 优先使用非公平锁:除非有特殊的公平性要求,否则非公平锁性能更好

  3. 合理使用tryLock():避免线程无限阻塞

    if (lock.tryLock(1, TimeUnit.SECONDS)) {
         
        try {
         
            // 业务逻辑
        } finally {
         
            lock.unlock();
        }
    } else {
         
        // 获取锁失败的处理逻辑
    }
    
  4. 读写锁的正确使用:读操作使用读锁,写操作使用写锁

5.2 常见陷阱

  1. 忘记释放锁:导致死锁,必须在finally中调用unlock()
  2. 锁重入次数不匹配:获取多少次锁,就要释放多少次锁
  3. 在Condition.await()前没有持有锁:会抛出IllegalMonitorStateException
  4. 读写锁使用错误:读操作使用写锁,导致并发性能下降
  5. 锁升级:持有读锁时获取写锁,会导致死锁
  6. 多个锁获取顺序不一致:导致死锁(遵循固定的获取顺序)

六、面试核心考点清单

  1. ReentrantLock的实现原理:基于AQS,state变量,CLH队列
  2. 公平锁与非公平锁的区别:获取流程、性能、饥饿问题
  3. 可重入性的实现:state计数器,线程持有判断
  4. Condition的作用与实现:替代wait/notify,多条件变量
  5. ReentrantReadWriteLock的实现原理:state拆分,读写锁互斥规则
  6. 锁降级机制:定义、目的、正确使用方式
  7. Lock与synchronized的区别:从实现、特性、性能、使用场景等方面
  8. AQS的核心思想:同步状态、CLH队列、独占/共享模式
  9. 死锁的产生条件与避免方法:互斥、持有并等待、不可剥夺、循环等待
  10. 各种锁的使用场景:根据业务特点选择合适的锁

一、可直接背诵的面试版核心考点(精简版)

1. Lock体系基础

  • 产生背景:解决synchronized的4大缺陷:不可中断、非公平、单一条件、无法超时获取
  • Lock接口核心方法lock()(阻塞)、lockInterruptibly()(可中断)、tryLock()(非阻塞)、tryLock(long, TimeUnit)(超时)、unlock()(必须finally)、newCondition()(多条件)
  • 核心实现类ReentrantLock(独占可重入)、ReentrantReadWriteLock(读写分离)、StampedLock(JDK8乐观读写)

2. ReentrantLock 必背考点

  • 核心特性:可重入、公平/非公平双模式、可中断、超时获取、多条件变量
  • 实现原理:基于AQS(抽象队列同步器),volatile int state记录锁状态,CLH双向队列存放等待线程
  • 公平vs非公平
    • 非公平(默认):先CAS抢锁,失败再排队,性能高,可能饥饿
    • 公平:直接排队,严格FIFO,性能低,无饥饿
  • 可重入实现:同一线程多次获取时state++,释放时state--state=0才真正释放
  • Condition:替代wait/notify,一个Lock可创建多个Condition,实现精准唤醒(如生产者消费者的notFull/notEmpty)

3. ReentrantReadWriteLock 必背考点

  • 设计思想:读写分离,读共享、写独占,适用于读多写少场景
  • state拆分:32位int,高16位=读锁计数,低16位=写锁计数
  • 核心规则
    • 写锁与所有锁互斥(读+写)
    • 读锁与读锁共享
    • 支持锁降级(写→读),不支持锁升级(读→写,会死锁)
  • 锁降级目的:保证数据可见性,避免写后读间隙被其他线程修改

4. Lock vs synchronized 核心区别(面试高频)

维度 synchronized Lock
实现层面 JVM内置,字节码(monitorenter/monitorexit) JDK层面,纯Java代码(AQS+CAS)
释放方式 自动(代码结束/异常) 手动(必须finally调用unlock())
公平性 仅非公平 支持公平/非公平
可中断性 不支持 支持(lockInterruptibly())
超时获取 不支持 支持(tryLock超时)
条件变量 1个(Object) 多个(Condition)
锁类型 仅独占锁 独占+共享(读写锁)
性能 低并发好,高并发差 高并发下显著更优

5. 最佳实践与常见陷阱

  • 铁律:Lock必须在finally中释放,否则死锁
  • 优先非公平锁:除非有严格公平性要求
  • tryLock优先:避免无限阻塞
  • 读写锁严格区分:读用读锁,写用写锁
  • 禁止锁升级:持有读锁时不能获取写锁
  • 锁顺序一致:多个锁按固定顺序获取,避免循环等待

二、StampedLock 补充分析(JDK8新增,面试高频对比)

1. 产生背景

解决ReentrantReadWriteLock写饥饿问题:当读线程非常多时,写线程可能长时间无法获取锁。

2. 核心设计思想

引入乐观读模式:读操作不需要加锁,仅通过一个戳记(stamp)验证数据是否被修改。如果验证通过,直接读取;如果验证失败,再升级为悲观读锁。

3. 三种核心模式

模式 特性 返回值 互斥规则
写锁(WriteLock) 独占锁,与所有锁互斥 非0戳记 与读锁、写锁都互斥
悲观读锁(ReadLock) 共享锁,与写锁互斥 非0戳记 与写锁互斥,与读锁共享
乐观读(OptimisticRead) 无锁,仅返回戳记 非0戳记 不与任何锁互斥

4. 乐观读使用示例

StampedLock lock = new StampedLock();
long stamp = lock.tryOptimisticRead(); // 获取乐观读戳记
// 读取数据
int data = this.data;
if (!lock.validate(stamp)) {
    // 验证戳记是否有效(数据是否被修改)
    // 戳记无效,升级为悲观读锁
    stamp = lock.readLock();
    try {
   
        data = this.data;
    } finally {
   
        lock.unlockRead(stamp);
    }
}
return data;

5. StampedLock vs ReentrantReadWriteLock 全面对比

特性 ReentrantReadWriteLock StampedLock
可重入性 支持 不支持
条件变量 支持 不支持
锁降级 支持 支持(写→悲观读)
锁升级 不支持 支持(乐观读→悲观读→写)
写饥饿 严重(读多写少) 大幅缓解
性能 读多写少较好 读多写少更优(乐观读无锁)
适用场景 读多写少,需要重入/条件变量 读多写少,追求极致性能

6. 使用注意事项

  • 乐观读不是锁,必须调用validate(stamp)验证数据一致性
  • 不支持重入,同一线程不能多次获取同一把锁
  • 不支持Condition条件变量
  • 写锁和悲观读锁必须使用对应的unlockWrite(stamp)/unlockRead(stamp)释放
  • 戳记无效时必须升级为悲观读锁,不能直接重试乐观读

三、终极面试答题框架(直接套用)

当面试官问"ReentrantLock和synchronized的区别"时,按以下顺序回答:

  1. 实现层面:JVM内置 vs JDK AQS+CAS
  2. 释放方式:自动 vs 手动(finally)
  3. 高级特性:公平性、可中断性、超时获取、多条件变量
  4. 锁类型:仅独占 vs 独占+共享
  5. 性能:低并发synchronized好,高并发Lock好
  6. 适用场景:简单场景用synchronized,需要高级特性用Lock
相关文章
|
2天前
|
安全 Java API
【Java基础】Java 8-21新特性:JDK21 LTS:虚拟线程、模式匹配switch、结构化并发、序列集合(附《思维导图》+《面试高频考点清单》)
本文系统梳理Java 8至21的演进脉络,聚焦JDK 21 LTS四大核心特性:虚拟线程(M:N轻量调度,百万级I/O并发)、模式匹配switch(类型+守卫+null安全)、结构化并发(父子任务生命周期绑定)、序列集合(统一有序集合操作)。兼顾版本战略、迁移实践与面试高频考点,助力高效掌握现代Java开发核心能力。
|
2月前
|
移动开发 前端开发 JavaScript
【贪吃蛇小游戏】 HTML (Canvas)+ JavaScript
这是一个基于 HTML5(Canvas)+JavaScript 开发的贪吃蛇小游戏,通过800×800画布实现蛇体绘制、食物生成、碰撞检测及方向控制,支持键盘操作与重新开始功能,代码结构清晰,适合初学者学习Web游戏开发。
893 11
|
2天前
|
人工智能 弹性计算 Serverless
阿里云最新AI产品优惠权益解析:千问旗舰模型助力AI落地,轻量云2核2G38元起,9.9元快速部署OpenClaw
阿里云2026年最新AI普惠权益,覆盖个人开发者、学生及企业用户。核心权益包括:阿里云百炼Token Plan支持多模型灵活切换,首购低至4.5折,标准/高级/尊享三档套餐满足不同用量需求;视频生成模型HappyHorse限时8折,720P每秒仅0.72元起;高校学生完成认证可领300元无门槛抵扣金;轻量应用服务器2核2G低至38元/年,9.9元可快速部署OpenClaw;另有超30款AI产品及7000万tokens免费试用,AI组合购套餐78元起,以及百炼"先用后返"最高返200元活动,全方位降低AI应用落地门槛。
|
2天前
|
人工智能 API 决策智能
解锁智能体新纪元:Qwen3.7-Max 正式发布,开启长程自主执行新时代
Qwen3.7-Max 是面向Agentic时代的全能基座模型,实现从“说得好”到“做得到”的范式跃迁。它以35小时全自主芯片优化、顶尖推理与编程能力(GPQA 92.4、SWE-80.4)、双模式推理及全栈Agent化架构,树立国产大模型新标杆。
|
23天前
|
消息中间件 NoSQL Kafka
【Kafka核心】消息投递语义、Exactly-Once实现、幂等性、事务消息
本文系统梳理Kafka消息一致性核心体系:以「不丢不重」为目标,详解At-Most-Once、At-Least-Once、Exactly-Once三类投递语义;深入剖析幂等性(单会话单分区去重)与事务机制(跨分区/跨会话原子性)的原理与配置;最终整合生产者、Broker、消费者三方协同,实现端到端Exactly-Once。附最佳实践与避坑指南。
|
12天前
|
存储 安全 关系型数据库
【MySQL】MySQL日志体系:redo log/undo log/binlog 三者区别、两阶段提交、如何保证数据一致性
MySQL三大核心日志:undo log(保障原子性,支持回滚与MVCC)、redo log(保障持久性,崩溃恢复,WAL机制)、binlog(保障可复制性,主从同步与数据恢复)。三者分属不同层级,协同实现ACID与高可用。
|
2天前
|
人工智能 缓存 运维
AI智能体协同实战:Hermes Agent+Claude Code接入阿里云百炼Token Plan完整教程
2026年,AI智能体已经从单一代码助手,进化为能够协同工作的虚拟开发团队。Hermes Agent与Claude Code的组合,成为当前最成熟、最高效的AI开发搭档:Hermes Agent负责任务规划、需求拆解、记忆沉淀与流程调度,扮演技术主管角色;Claude Code专注代码生成、文件修改、命令执行与工程落地,承担核心开发工作。二者配合,可实现从需求分析到代码落地的全流程自动化,大幅提升研发效率。
102 0
|
12天前
|
SQL 存储 运维
【MySQL】高可用:主从复制原理、主从延迟解决方案、半同步复制、MGR
本文系统梳理MySQL高可用知识体系,涵盖主从复制原理、GTID、半同步(AFTER_SYNC)、MGR集群等核心机制,深入解析延迟成因与优化策略,并对比选型方案,助力构建稳定、一致、自动恢复的高可用数据库架构。
|
12天前
|
SQL 算法 关系型数据库
【MySQL】MVCC多版本并发控制:核心原理、Read View、undo log版本链、RC/RR隔离级别的差异控制(附《高频面试题》+流程图)
MySQL MVCC是InnoDB实现高并发的核心机制,通过undo log版本链与Read View可见性判断,使读不加锁、读写互不阻塞。它支撑RC(每次查询新建Read View)和RR(事务首次查询创建并复用Read View)隔离级别,在解决不可重复读与幻读的同时,兼顾性能与一致性。
|
12天前
|
存储 关系型数据库 MySQL
【MySQL】索引核心:B+树索引原理、为什么MySQL用B+树而不用B树/红黑树?
本文深度解析MySQL索引核心——B+树原理与选型逻辑,涵盖索引本质、B+树结构特性、聚簇/二级/联合索引实现,并对比B树、红黑树、哈希等结构,阐明B+树在磁盘IO、范围查询、查询稳定性上的不可替代性。

热门文章

最新文章