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

Java并发包中的管程:Lock和Condition

1. Java并发包中的管程:Lock和Condition

1.1. Lock 的核心意义:再造管程

Java 原生的 synchronized 关键字已经是管程的一种实现,那为什么还要提供 Lock 接口?

原因是:

Lock 弥补了 synchronized 的三大缺陷,尤其是在解决“死锁”中的不可抢占条件时具有更强的灵活性。

三种解决“不可抢占”的能力(也是 Lock 的优势):

1. 可中断获取锁

void lockInterruptibly() throws InterruptedException;
  • 遇到死锁时可通过中断来释放已有资源。

2. 支持超时的获取锁

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  • 限时等待,避免无限期阻塞。

3. 非阻塞地获取锁

boolean tryLock();
  • 获取不到立即返回,避免线程挂起。

1.2. Lock 的可见性保障原理

问题:

在使用 Lock 的并发场景中,如何确保对共享变量修改的可见性?

解决方案:

基于 Java 内存模型的 Happens-Before 原则ReentrantLock 内部的 volatile state 实现:

  • 解锁前后的顺序性保证(线程内)
  • volatile 变量规则(state 的读写)
  • 传递性规则:T1 修改变量 → 解锁 → T2 加锁 → 可见

1.3. 可重入锁(ReentrantLock)、公平锁与非公平锁

1. 可重入锁(ReentrantLock)

可重入锁的定义:指的是线程可以重复获取同一把锁。

示例:

例如下面代码中,当线程 T1 执行到 ① 处时,已经获取到了锁 rtl ,当在 ① 处调用 get() 方法时,会在 ② 再次对锁 rtl 执行加锁操作。此时,如果锁 rtl 是可重入的,那么线程 T1 可以再次加锁成功;如果锁 rtl 是不可重入的,那么线程 T1 此时会被阻塞。

class X {private final Lock rtl =new ReentrantLock();int value;public int get() {// 获取锁rtl.lock();         ②try {return value;} finally {// 保证锁能释放rtl.unlock();}}public void addOne() {// 获取锁rtl.lock();  try {value = 1 + get(); ①} finally {// 保证锁能释放rtl.unlock();}}
}
  • 可重入锁可避免同一个线程因为重复获取锁而造成死锁。

可重入函数(线程安全的):

重入函数,指的是多个线程可以同时调用该函数,每个线程都能得到正确结果;同时在一个线程内支持线程切换,无论被切换多少次,结果都是正确的。

2. 公平锁与非公平锁

区别:

  • 公平锁:先到先得,排队唤醒,避免线程“饿死”
  • 非公平锁(默认):允许插队,有更好的性能表现
// 无参构造函数:默认非公平锁
public ReentrantLock() {sync = new NonfairSync();
}
// 根据公平策略参数创建锁
public ReentrantLock(boolean fair){sync = fair ? new FairSync() : new NonfairSync();
}

1.4. 用锁的最佳实践

最佳实践(出自 Doug Lea):

  1. 只在更新对象的成员变量时加锁
  2. 只在访问可变成员变量时加锁
  3. 不要在加锁后调用其他对象的方法
    • 可能造成不可控延迟(例如 IO、sleep)
    • 可能加其他锁,引发死锁风险

补充建议:

  • 减少锁的持有时间(快速释放)
  • 降低锁的粒度(尽可能小范围加锁)
  • 避免锁的嵌套调用

总结:

  • Lock 提供了比 synchronized 更丰富和灵活的控制机制。
  • 使用 Lock 编写并发程序时,应牢记:
    • 能不能中断?
    • 能不能超时?
    • 能不能快速失败?
  • 最好的并发代码是简单、清晰、易于分析的。

1.5. Condition的概念

Lock 与 synchronized 的区别:

  • 支持中断响应(lock.lockInterruptibly()
  • 支持超时获取锁(tryLock(timeout)
  • 支持非阻塞获取锁(tryLock()

Condition 是什么?

  • 管程中的条件变量
  • Object.wait/notify 相比,Condition 支持多个条件变量,适用于更复杂的并发场景(如阻塞队列:notEmptynotFull)。

Condition 使用示例:阻塞队列

public class BlockedQueue<T> {final Lock lock = new ReentrantLock();final Condition notFull = lock.newCondition();   // 队列不满final Condition notEmpty = lock.newCondition();  // 队列不空void enq(T x) {lock.lock();try {while (队列已满) {notFull.await();}// 执行入队操作...notEmpty.signal();  // 通知可以出队} finally {lock.unlock();}}void deq() {lock.lock();try {while (队列为空) {notEmpty.await();}// 执行出队操作...notFull.signal();  // 通知可以入队} finally {lock.unlock();}}
}
  • 注意:Lock 和 Condition 实现的管程,线程等待和通知需要调用 await()、signal()、signalAll(),它们的语义和 wait()、notify()、notifyAll() 是相同的。

1.6. 异步与同步的本质

同步: 调用方必须等待结果返回(如函数调用阻塞当前线程)。

异步: 调用方无需等待结果,调用立即返回,后续结果通过回调或其他方式通知。

举例:

pai1M(); // 如果阻塞等结果 -> 同步
printf("hello world"); // 若立即执行,不等待上面 -> 异步

实现异步的两种方式:

  1. 调用方创建线程: 在子线程中调用(常见于主线程不被阻塞的情况)。
  2. 被调用方创建线程: 方法中主动异步处理逻辑,主线程立即 return。

1.7. 异步转同步

异步转同步:

调用本来是异步执行的,但我们人为“阻塞住”当前线程,等它的结果返回后再继续执行,从而模拟出同步调用的效果。

知识点

说明

异步调用

不等待结果,直接返回

同步调用

等待结果,阻塞线程

Dubbo 的异步转同步

发送请求是异步的,但通过 .get()进行线程等待

核心实现

Lock+ Condition,通过 await()阻塞、signal()唤醒

好处

兼顾性能(异步发送)和易用性(同步获取结果)

1.8. Lock-Condition例子

示例功能:

  • 有一个共享队列
  • 生产者线程往队列里放数据;
  • 消费者线程从队列中取数据;
  • 使用 ReentrantLockCondition 实现线程间的等待和唤醒机制

Java代码:

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;public class LockConditionExample {// 定义一个固定大小的共享队列private static final int CAPACITY = 5;private final Queue<Integer> queue = new LinkedList<>();// 显式锁对象private final Lock lock = new ReentrantLock();// 队列满时:生产者等待的条件private final Condition notFull = lock.newCondition();// 队列空时:消费者等待的条件private final Condition notEmpty = lock.newCondition();// 生产者方法public void produce(int value) throws InterruptedException {lock.lock(); // 获取锁try {// 如果队列已满,则等待 notFull 条件while (queue.size() == CAPACITY) {System.out.println("队列满了,生产者等待...");notFull.await(); // 进入等待并释放锁}// 加入元素到队列queue.offer(value);System.out.println("生产者生产了: " + value);// 通知消费者:队列非空notEmpty.signal();} finally {lock.unlock(); // 释放锁}}// 消费者方法public int consume() throws InterruptedException {lock.lock(); // 获取锁try {// 如果队列为空,则等待 notEmpty 条件while (queue.isEmpty()) {System.out.println("队列空了,消费者等待...");notEmpty.await(); // 进入等待并释放锁}// 从队列中取出一个元素int value = queue.poll();System.out.println("消费者消费了: " + value);// 通知生产者:队列不满了notFull.signal();return value;} finally {lock.unlock(); // 释放锁}}// 主函数 - 启动生产者和消费者线程public static void main(String[] args) {LockConditionExample example = new LockConditionExample();// 生产者线程Thread producer = new Thread(() -> {int value = 0;try {while (true) {example.produce(value++);Thread.sleep(500); // 模拟生产速度}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 消费者线程Thread consumer = new Thread(() -> {try {while (true) {example.consume();Thread.sleep(1000); // 模拟消费速度}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 启动线程producer.start();consumer.start();}
}

疑问:

它们共享一个锁,但它们仍然能「协作式」地工作。不是并行执行临界区的代码,而是轮流进入、协同完成任务。

这意味着:
同一时刻只能有一个线程(无论是生产者还是消费者)进入加锁的代码块,也就是说:

  • 生产者线程进入produce() 方法时,消费者线程如果也想进入 consume() 方法,就必须等待
  • 同样,反过来也一样。

那为什么说它们“可以协同工作”?

因为它们用 Condition 做了同步等待与唤醒

具体情况如下:

场景

行为

队列满时

生产者执行 notFull.await()→ 放弃锁 → 等待消费者消费后唤醒它

队列有空位了

消费者 signal()唤醒了 notFull.await()处的生产者

队列空时

消费者执行 notEmpty.await() → 放弃锁 → 等待生产者生产后唤醒它

队列有数据了

生产者 signal()唤醒了notEmpty.await()处的消费者

  • 所以虽然它们使用同一个锁,但通过Condition 的等待和唤醒机制,实现了“互不干扰”的轮流工作流程 —— 类似于两个人轮流进一个房间传递东西,每次只能一个人进去,但彼此不会阻碍工作流程

相关文章:

  • echarts树状图与vue3
  • 微软推出SQL Server 2025技术预览版,深化人工智能应用集成
  • “一代更比一代强”:现代 RAG 架构的演进之路
  • 408第一季 - 数据结构 - 栈与队列
  • Python读取阿里法拍网的html+解决登录cookie
  • 创客匠人:如何通过创始人IP打造实现知识变现与IP变现的长效增长?
  • 《DeepSeek R1-0528与ChatGPT o3对比分析》
  • clickhouse 学习总结
  • 第十届电子技术和信息科学国际学术会议(ICETIS 2025)
  • 郑州工程技术学院赴埃文科技开展访企拓岗促就业活动
  • 消防一体化安全管控平台:构建消防“一张图”和APP统一管理
  • 如何在没有 iTunes 的情况下备份 iPhone
  • MySQL体系架构解析(三):MySQL数据存储的揭秘
  • Gerrit相对Git提供了一个特有的命名空间“refs/for/”用来定义我们的提交上传到哪个branch
  • C#报错 iText.Kernel.Exceptions.PdfException: ‘Unknown PdfException
  • pyinstaller打包遇到报错,和pathlib冲突
  • 实战项目中文影评情感分析系统
  • 电子电路基础2(杂乱)
  • 全球数控金属切削机床市场:现状、趋势与应对策略
  • 火语言RPA--选择元素工具使用方法
  • 济南哪里有做网站的公司/网络平台建设及运营方案
  • jtbc网站内容管理系统/微商软文推广平台
  • wordpress前端怎么写/优化生育政策
  • google做网站框架/中国网民博客 seo
  • 江苏优化网站/重庆百度推广电话
  • 可以做网页的网站/网站产品怎么优化