乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

Lock 与显式锁详解

场景:大翔最近被一个需求搞得很头疼——飞翔科技的连接池管理系统需要实现"尝试获取资源,超时就放弃"的功能。synchronized 只能死等,根本做不到。

大翔:"synchronized 要么获取锁,要么阻塞到天荒地老。有没有能超时放弃、能被打断的锁?"

白歌(放下咖啡杯):"JDK 5 就引入了 java.util.concurrent.locks.Lock 接口,把 synchronized 做不到的——tryLock 超时、lockInterruptibly、公平锁——全补上了。"

小崔(眼睛一亮):"那岂不是可以替代 synchronized?"

Frank:"Not exactly. Lock gives you more control, but you must manually unlock — even in the finally block. Forget unlock? You have a deadlock. synchronized automatically releases the lock."

朱璐:"而且 Lock 的实现——ReentrantLock——底层是基于 AQS(AbstractQueuedSynchronizer)框架的。理解了 AQS,就理解了整个 JUC 并发包的设计哲学。"

白歌:"不过 AQS 很深,我们今天先聚焦于 Lock 接口和 ReentrantLock 的实战用法。Condition 的生产者-消费者模式也别漏。"

大翔:"好,那就开讲。"


一、定义速查表

属性说明
Lock 接口java.util.concurrent.locks.Lock,提供比 synchronized 更灵活的锁操作。核心方法:lock(), tryLock(), lockInterruptibly(), unlock(), newCondition()
ReentrantLockLock 接口的标准实现,支持可重入、公平/非公平模式。默认非公平锁(吞吐量优先),基于 AQS 框架实现
公平锁(Fair Lock)按请求顺序分配锁,先到先得。new ReentrantLock(true),吞吐量较低但避免饥饿
非公平锁(Unfair Lock)允许"插队"获取锁,吞吐量更高。new ReentrantLock(false) 或默认构造,JDK 8 默认行为
tryLock()尝试获取锁,立即返回 true/false;支持超时版本 tryLock(long timeout, TimeUnit unit),是 Lock 相比 synchronized 的核心优势
lockInterruptibly()可被中断的锁获取,被中断时抛出 InterruptedException。synchronized 不可中断
Condition条件变量,通过 lock.newCondition() 创建。提供 await(), signal(), signalAll() 方法,对应 Object 的 wait(), notify(), notifyAll()
AQS(AbstractQueuedSynchronizer)JUC 并发框架的核心,基于 FIFO 双向队列 + volatile int state 实现的同步器框架。ReentrantLock、Semaphore、CountDownLatch 等均基于 AQS
可重入(Reentrant)同一线程可多次获取同一把锁。ReentrantLock 内部维护 state 计数器和 exclusiveOwnerThread 记录持有线程
锁绑定多条件一个 Lock 可以创建多个 Condition,实现更精细的线程等待/唤醒控制。synchronized 只有一个隐式条件

二、Mermaid 图解

2.1 ReentrantLock lock() 获取锁流程

2.2 Lock 接口类层次结构


三、synchronized vs Lock 完整对比

对比维度synchronizedLock(ReentrantLock)
实现层面JVM 内置,Monitor 机制JDK API,基于 AQS
锁释放自动(代码块结束/异常)必须手动 unlock()(finally 中)
可中断获取❌ 不支持✅ lockInterruptibly()
超时获取❌ 不支持✅ tryLock(timeout, unit)
非阻塞尝试❌ 不支持✅ tryLock()
公平锁❌ 仅非公平✅ 可选公平/非公平
条件变量1 个隐式条件(wait/notify)多个 Condition
查询锁状态❌ 不支持✅ isLocked(), getHoldCount() 等
性能(JDK 8)优化后接近略优(尤其高并发)
死锁风险较低(自动释放)较高(忘记 unlock)
使用复杂度低高

四、完整代码示例

示例 1:飞翔科技资源池管理 —— tryLock 超时获取

场景:飞翔科技的数据库连接池只有 3 个连接,多个业务线程竞争获取。使用 tryLock(timeout) 实现"超时获取不到就放弃"。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 飞翔科技 - 资源池管理 tryLock 超时演示
 * 
 * 场景:数据库连接池只有 3 个连接(每个连接一把锁)
 *       多个业务线程尝试获取连接,2 秒超时放弃
 *       大翔(10001)、白歌(10002)、小崔(10003)、Frank(10004)
 */
public class ResourcePoolDemo {

    // 资源池:3 个连接,每个连接对应一把锁
    static class ConnectionPool {
        private final ReentrantLock[] locks;
        private final String[] connectionNames;

        public ConnectionPool(int size) {
            locks = new ReentrantLock[size];
            connectionNames = new String[size];
            for (int i = 0; i < size; i++) {
                locks[i] = new ReentrantLock();
                connectionNames[i] = "DB-Conn-" + (i + 1);
            }
        }

        /**
         * 尝试获取连接,超时放弃
         * @param workerName 业务线程名称
         * @param timeout 超时时间(秒)
         * @return 获取到的连接名称,null 表示获取失败
         */
        public String acquireConnection(String workerName, long timeout) {
            for (int i = 0; i < locks.length; i++) {
                try {
                    // ★ tryLock 超时 — 尝试获取锁,超时则放弃
                    if (locks[i].tryLock(timeout, TimeUnit.SECONDS)) {
                        System.out.printf("[%s] ✓ 获取连接 %s\n", workerName, connectionNames[i]);
                        return connectionNames[i];
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return null;
                }
            }
            System.out.printf("[%s] ✗ 所有连接都被占用,超时放弃\n", workerName);
            return null;
        }

        /**
         * 释放连接
         */
        public void releaseConnection(String connName) {
            for (int i = 0; i < connectionNames.length; i++) {
                if (connectionNames[i].equals(connName) && locks[i].isHeldByCurrentThread()) {
                    locks[i].unlock();
                    System.out.printf("[释放] %s 已归还\n", connName);
                    return;
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ConnectionPool pool = new ConnectionPool(3);
        System.out.println("========== 飞翔科技连接池管理 (tryLock 超时) ==========\n");
        System.out.println("连接池大小: 3 | 超时时间: 2秒 | 业务线程: 4\n");

        // 线程1:大翔(10001) — 执行业务查询
        Thread t1 = new Thread(() -> {
            String conn = pool.acquireConnection("大翔-10001", 2);
            if (conn != null) {
                try { Thread.sleep(3000); } catch (InterruptedException e) { }
                pool.releaseConnection(conn);
            }
        }, "大翔");

        // 线程2:白歌(10002) — 执行报表生成
        Thread t2 = new Thread(() -> {
            String conn = pool.acquireConnection("白歌-10002", 2);
            if (conn != null) {
                try { Thread.sleep(3000); } catch (InterruptedException e) { }
                pool.releaseConnection(conn);
            }
        }, "白歌");

        // 线程3:小崔(10003) — 执行数据导入
        Thread t3 = new Thread(() -> {
            String conn = pool.acquireConnection("小崔-10003", 2);
            if (conn != null) {
                try { Thread.sleep(3000); } catch (InterruptedException e) { }
                pool.releaseConnection(conn);
            }
        }, "小崔");

        // 线程4:Frank(10004) — 执行数据导出(第4个线程,晚启动)
        Thread t4 = new Thread(() -> {
            try { Thread.sleep(500); } catch (InterruptedException e) { }
            String conn = pool.acquireConnection("Frank-10004", 2);
            if (conn != null) {
                try { Thread.sleep(3000); } catch (InterruptedException e) { }
                pool.releaseConnection(conn);
            }
        }, "Frank");

        t1.start(); t2.start(); t3.start(); t4.start();

        t1.join(); t2.join(); t3.join(); t4.join();
        System.out.println("\n========== 所有业务处理完毕 ==========");
    }
}

控制台输出:

========== 飞翔科技连接池管理 (tryLock 超时) ==========

连接池大小: 3 | 超时时间: 2秒 | 业务线程: 4

[大翔-10001] ✓ 获取连接 DB-Conn-1
[白歌-10002] ✓ 获取连接 DB-Conn-2
[小崔-10003] ✓ 获取连接 DB-Conn-3
[Frank-10004] ✗ 所有连接都被占用,超时放弃       ← 第4个线程超时放弃!
[释放] DB-Conn-1 已归还
[释放] DB-Conn-2 已归还
[释放] DB-Conn-3 已归还

========== 所有业务处理完毕 ==========

示例 2:飞翔科技生产者-消费者 —— Condition 精确唤醒

场景:飞翔科技的薪资计算系统,大翔(生产者)计算员工薪资(8888.88 元基数),白歌(消费者)审核发放。使用 Condition 实现精确的线程间通信。

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 飞翔科技 - 生产者消费者 Condition 精确唤醒
 * 
 * 场景:大翔(10001) 计算薪资放入队列,白歌(10002) 从队列取出发放
 *       队列容量 5,使用 Lock + Condition 实现
 *       notFull: 队列未满条件,notEmpty: 队列非空条件
 */
public class ProducerConsumerConditionDemo {

    static class SalaryQueue {
        private final Queue<Float> queue = new LinkedList<>();
        private final int capacity;

        private final Lock lock = new ReentrantLock();
        // ★ 两个 Condition — 精确控制生产者和消费者的唤醒
        private final Condition notFull = lock.newCondition();   // 队列未满
        private final Condition notEmpty = lock.newCondition();  // 队列非空

        public SalaryQueue(int capacity) {
            this.capacity = capacity;
        }

        /**
         * 生产者:大翔计算薪资并放入队列
         */
        public void produce(float salary, int employeeId) throws InterruptedException {
            lock.lock();
            try {
                // ★ while 循环防止虚假唤醒
                while (queue.size() == capacity) {
                    System.out.printf("[生产者] 队列已满,等待消费者消费...\n");
                    notFull.await();  // 等待 notFull 条件
                }

                queue.offer(salary);
                System.out.printf("[生产者-大翔] 计算工号%d 薪资: %.2f 元, 队列: %d/%d\n",
                        employeeId, salary, queue.size(), capacity);

                // ★ 唤醒消费者 — 精确唤醒等待 notEmpty 的线程
                notEmpty.signal();
            } finally {
                lock.unlock();  // ★ 必须在 finally 中释放锁
            }
        }

        /**
         * 消费者:白歌从队列取出薪资并发放
         */
        public float consume() throws InterruptedException {
            lock.lock();
            try {
                while (queue.isEmpty()) {
                    System.out.printf("[消费者] 队列为空,等待生产者生产...\n");
                    notEmpty.await();  // 等待 notEmpty 条件
                }

                float salary = queue.poll();
                System.out.printf("[消费者-白歌] 发放薪资: %.2f 元, 队列: %d/%d\n",
                        salary, queue.size(), capacity);

                // ★ 唤醒生产者 — 精确唤醒等待 notFull 的线程
                notFull.signal();
                return salary;
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SalaryQueue salaryQueue = new SalaryQueue(5);
        System.out.println("========== 飞翔科技薪资计算系统 (Condition) ==========\n");

        // 生产者线程:大翔(10001)
        Thread producer = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    int employeeId = 10001 + i;
                    float salary = 8888.88f + i * 100;  // 不同员工的薪资
                    salaryQueue.produce(salary, employeeId);
                    Thread.sleep(300); // 模拟计算耗时
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Producer-大翔");

        // 消费者线程:白歌(10002)
        Thread consumer = new Thread(() -> {
            try {
                float totalPaid = 0;
                for (int i = 0; i < 10; i++) {
                    float salary = salaryQueue.consume();
                    totalPaid += salary;
                    Thread.sleep(500); // 模拟发放耗时
                }
                System.out.printf("\n[消费者-白歌] 总计发放薪资: %.2f 元\n", totalPaid);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Consumer-白歌");

        producer.start();
        consumer.start();

        producer.join();
        consumer.join();

        System.out.println("\n========== 本月薪资发放完毕 ==========");
    }
}

控制台输出:

========== 飞翔科技薪资计算系统 (Condition) ==========

[生产者-大翔] 计算工号10001 薪资: 8888.88 元, 队列: 1/5
[生产者-大翔] 计算工号10002 薪资: 8988.88 元, 队列: 2/5
[消费者-白歌] 发放薪资: 8888.88 元, 队列: 1/5
[生产者-大翔] 计算工号10003 薪资: 9088.88 元, 队列: 2/5
[消费者-白歌] 发放薪资: 8988.88 元, 队列: 1/5
[生产者-大翔] 计算工号10004 薪资: 9188.88 元, 队列: 2/5
[生产者-大翔] 计算工号10005 薪资: 9288.88 元, 队列: 3/5
[消费者-白歌] 发放薪资: 9088.88 元, 队列: 2/5
...(生产消费交替进行)
[生产者-大翔] 计算工号10010 薪资: 9788.88 元, 队列: 1/5
[消费者-白歌] 发放薪资: 9788.88 元, 队列: 0/5

[消费者-白歌] 总计发放薪资: 93388.80 元

========== 本月薪资发放完毕 ==========

五、ReentrantLock 高级特性

5.1 公平锁 vs 非公平锁

/**
 * 飞翔科技 - 公平锁 vs 非公平锁对比
 */
public class FairVsUnfairDemo {

    public static void main(String[] args) {
        // 公平锁:先到先得
        ReentrantLock fairLock = new ReentrantLock(true);
        // 非公平锁:允许插队(默认)
        ReentrantLock unfairLock = new ReentrantLock(false);

        System.out.println("========== 飞翔科技锁模式对比 ==========\n");

        // 公平锁测试
        System.out.println("--- 公平锁 ---");
        testLock("公平锁", fairLock);

        // 非公平锁测试
        System.out.println("\n--- 非公平锁 ---");
        testLock("非公平锁", unfairLock);
    }

    private static void testLock(String label, ReentrantLock lock) {
        for (int i = 0; i < 5; i++) {
            final int workerId = 10001 + i;
            new Thread(() -> {
                System.out.printf("[工号%d] 请求 %s...\n", workerId, label);
                lock.lock();
                try {
                    System.out.printf("[工号%d] ✓ 获得 %s\n", workerId, label);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    lock.unlock();
                }
            }).start();
        }
        try { Thread.sleep(2000); } catch (InterruptedException e) { }
    }
}

5.2 lockInterruptibly 可中断获取

/**
 * 飞翔科技 - lockInterruptibly 可中断锁获取演示
 */
public class LockInterruptDemo {

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();

        // 大翔(10001) 先获取锁并长时间持有
        Thread holding = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("[大翔-10001] 获取锁,将持有 5 秒...");
                Thread.sleep(5000);
                System.out.println("[大翔-10001] 释放锁");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        }, "大翔-10001");

        // 小崔(10003) 尝试获取锁,但可以被中断
        Thread waiting = new Thread(() -> {
            try {
                System.out.println("[小崔-10003] 尝试获取锁(可中断)...");
                lock.lockInterruptibly();  // ★ 可被中断的锁获取
                try {
                    System.out.println("[小崔-10003] 获取锁成功");
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                System.out.println("[小崔-10003] 等待被中断,放弃获取锁");
            }
        }, "小崔-10003");

        holding.start();
        Thread.sleep(500); // 确保大翔先获取锁
        waiting.start();
        Thread.sleep(1000); // 等 1 秒后中断小崔
        System.out.println("[管理员] 中断小崔的等待...");
        waiting.interrupt();

        holding.join();
        waiting.join();
        System.out.println("\n========== 演示结束 ==========");
    }
}

六、易错场景

❌ 错误 1:忘记在 finally 中释放锁

// ✗ 错误:如果临界区抛异常,unlock() 不会执行 → 锁永远不会释放!
ReentrantLock lock = new ReentrantLock();
lock.lock();
doSomething();  // 如果这里抛异常...
lock.unlock();  // ...这行不会执行!

// ✓ 正确:unlock 必须在 finally 中
lock.lock();
try {
    doSomething();
} finally {
    lock.unlock();  // 无论如何都会释放锁
}

❌ 错误 2:Condition 的 await 用 if 而不是 while

// ✗ 错误:if 只检查一次,虚假唤醒后不会重新检查条件
lock.lock();
try {
    if (queue.isEmpty()) {
        condition.await();  // 被虚假唤醒后直接往下执行,可能队列仍为空!
    }
    queue.poll();  // 可能抛出 NoSuchElementException
} finally {
    lock.unlock();
}

// ✓ 正确:使用 while 循环,每次唤醒都重新检查条件
lock.lock();
try {
    while (queue.isEmpty()) {  // ★ while 循环防虚假唤醒
        condition.await();
    }
    queue.poll();
} finally {
    lock.unlock();
}

❌ 错误 3:signal() 和 signalAll() 用错

// ✗ 错误:多生产者-多消费者场景用 signal() 可能唤醒同类型线程导致死锁
// 例如:消费者唤醒消费者,两者都等待 notEmpty → 永远无法唤醒
notEmpty.signal();  // 可能唤醒另一个消费者

// ✓ 正确:不确定时统一使用 signalAll()
notEmpty.signalAll();  // 唤醒所有等待线程,让它们竞争

❌ 错误 4:tryLock 后不检查返回值

// ✗ 错误:tryLock 返回 false 时仍然操作共享资源
ReentrantLock lock = new ReentrantLock();
lock.tryLock();  // 忽略了返回值!
sharedResource++;  // ⚠ 可能没有获取到锁!
lock.unlock();     // ⚠ 可能抛出 IllegalMonitorStateException

// ✓ 正确:必须在 tryLock 成功后才操作
if (lock.tryLock()) {
    try {
        sharedResource++;
    } finally {
        lock.unlock();
    }
} else {
    // 处理获取失败的情况
}

七、面试考点

Q1:Lock 和 synchronized 的区别?什么时候用 Lock?

A:

维度synchronizedLock
获取锁阻塞式,不可中断可超时、可中断、可非阻塞尝试
释放自动手动(finally)
公平性非公平可选公平/非公平
条件1 个隐式条件多个 Condition

使用 Lock 的场景:

  • 需要 tryLock 超时获取(如连接池、资源池)
  • 需要可中断的锁获取(lockInterruptibly)
  • 需要公平锁(避免线程饥饿)
  • 需要多个条件变量(如复杂生产者-消费者)
  • 需要查询锁状态(isLocked(), getHoldCount() 等)

使用 synchronized 的场景:

  • 简单的临界区保护
  • 不想手动管理锁释放
  • 代码简洁性优先

Q2:ReentrantLock 的公平锁和非公平锁实现区别?

A:两者都基于 AQS 实现,区别在 tryAcquire 方法:

  • 非公平锁(默认):调用 lock() 时先 CAS 尝试抢锁(compareAndSetState(0, 1)),失败再进入 AQS 队列排队。已入队的线程可能被新来的线程"插队"
  • 公平锁:调用 lock() 时直接进入 AQS 队列,只有队列为空或当前线程是第一个等待者时才 CAS 抢锁。严格按 FIFO 顺序

非公平锁吞吐量更高(减少线程切换),公平锁避免饥饿。JDK 8 默认非公平。

Q3:Condition 的 await/signal 与 Object 的 wait/notify 有什么区别?

A:

对比维度ConditionObject wait/notify
所属对象Lock 创建Object 内置
需要持有锁Lock.lock()synchronized
多条件支持✅ 一个 Lock 多个 Condition❌ 一个对象一个等待集
精确唤醒✅ signal() 唤醒特定 Condition 的等待线程❌ notify() 随机唤醒一个
中断响应await() 响应中断wait() 响应中断

Q4:什么是 AQS?它的核心设计思想是什么?

A:AQS(AbstractQueuedSynchronizer)是 JUC 并发框架的核心基类,基于 FIFO 双向队列 + volatile int state 实现:

  • state:volatile int,表示同步状态(锁的持有计数、信号量许可数等)
  • CLH 队列:双向链表,存储等待线程的 Node 节点
  • 模板方法模式:tryAcquire(), tryRelease() 等由子类实现,acquire(), release() 等由 AQS 提供

ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock 等均基于 AQS。理解 AQS 是理解整个 JUC 包的关键。


大翔(满意地合上笔记本):"Lock 的强大在于灵活——tryLock 超时、Condition 多条件、lockInterruptibly 可中断——这些都是 synchronized 做不到的。"

白歌:"但灵活是把双刃剑。记住:unlock 必须放 finally,await 必须用 while 循环。"

Frank:"And underneath it all is AQS — the framework that powers the entire java.util.concurrent package. Master AQS, and you master JUC."

朱璐:"下一章我们回到基础——wait/notify。虽然 Lock 更强大,但理解 Object 的内置等待/通知机制是面试必考题。"

上一页
synchronized 详解
下一页
volatile 详解