首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
当前位置:
首页>>
技术小册>>
Java并发编程实战
小册名称:Java并发编程实战
### 章节 31 | Guarded Suspension模式:等待唤醒机制的规范实现 在Java并发编程的广阔领域中,等待唤醒机制是实现线程间协作与同步的关键技术之一。它允许一个或多个线程在某些条件未满足时暂停执行(等待),并在这些条件被其他线程改变(唤醒)后继续执行。这种机制对于构建高效、可靠的并发应用至关重要。本章将深入探讨Guarded Suspension(受保护挂起)模式,它是等待唤醒机制的一种规范化实现方式,旨在提升代码的可读性、可维护性和效率。 #### 31.1 引言 在Java中,`wait()` 和 `notify()/notifyAll()` 方法是实现等待唤醒机制的原生API,它们定义在`java.lang.Object`类中,因此所有Java对象都可以作为同步锁使用这些机制。然而,直接使用这些方法容易出错,因为它们必须被包裹在同步块或同步方法中,且调用时存在多种潜在的陷阱,如条件竞争、虚假唤醒等。Guarded Suspension模式通过封装这些原生API,提供了一种更为安全和易于理解的实现方式。 #### 31.2 Guarded Suspension模式概述 Guarded Suspension模式的核心思想是:在循环中检查某个条件(称为“守卫条件”),如果条件不满足,则调用等待方法挂起当前线程;当条件由其他线程改变后,通过唤醒方法唤醒等待的线程,并重新检查条件。这种模式的关键在于确保在条件满足之前,线程保持挂起状态,同时处理虚假唤醒的情况。 **关键组件**: 1. **锁对象**:作为同步的基础,通常是共享资源或专门用于同步的某个对象。 2. **守卫条件**:线程等待或继续执行所依赖的条件。 3. **等待方法**:当守卫条件不满足时,线程会调用此方法挂起自己。 4. **唤醒方法**:当守卫条件可能已改变时,其他线程会调用此方法唤醒等待的线程。 #### 31.3 示例实现 假设我们有一个简单的生产者-消费者问题,其中有一个固定大小的缓冲区用于存储产品。生产者线程生成产品并尝试放入缓冲区,如果缓冲区已满,则生产者必须等待;消费者线程从缓冲区取出产品,如果缓冲区为空,则消费者必须等待。 **步骤1:定义共享资源及守卫条件** ```java public class BoundedBuffer<T> { private final Object lock = new Object(); private final T[] buffer; private int count = 0; private int putPos = 0; private int takePos = 0; public BoundedBuffer(int capacity) { buffer = (T[]) new Object[capacity]; } // 守卫条件:缓冲区未满 private boolean notFull() { return count < buffer.length; } // 守卫条件:缓冲区非空 private boolean notEmpty() { return count > 0; } // ... } ``` **步骤2:实现等待和唤醒逻辑** ```java // 生产者方法 public void put(T item) throws InterruptedException { synchronized (lock) { while (!notFull()) { // 循环检查守卫条件 lock.wait(); // 条件不满足时挂起 } // 执行放入操作... buffer[putPos] = item; putPos = (putPos + 1) % buffer.length; ++count; lock.notifyAll(); // 唤醒可能等待的消费者 } } // 消费者方法 public T take() throws InterruptedException { synchronized (lock) { while (!notEmpty()) { // 循环检查守卫条件 lock.wait(); // 条件不满足时挂起 } // 执行取出操作... T item = buffer[takePos]; buffer[takePos] = null; // 可选:清理内存 takePos = (takePos + 1) % buffer.length; --count; lock.notifyAll(); // 唤醒可能等待的生产者或其他消费者 return item; } } ``` #### 31.4 虚假唤醒的处理 在Java的`wait()`方法中,线程可能会在没有其他线程调用`notify()`或`notifyAll()`的情况下被唤醒,这被称为“虚假唤醒”。因此,在Guarded Suspension模式中,我们总是将等待操作放在`while`循环中,而不是`if`语句中,以确保只有在守卫条件真正满足时才继续执行。 #### 31.5 优点与局限性 **优点**: - **代码清晰**:通过封装等待唤醒逻辑,使代码更加简洁易懂。 - **减少错误**:自动处理虚假唤醒,减少因直接使用`wait()`和`notify()`导致的错误。 - **灵活性**:可以根据需要轻松调整守卫条件和同步逻辑。 **局限性**: - **性能开销**:每次唤醒后都需要重新检查条件,可能增加CPU使用率。 - **死锁风险**:尽管Guarded Suspension模式本身不直接导致死锁,但错误的同步逻辑或锁使用方式仍可能引发死锁。 #### 31.6 高级话题:使用`java.util.concurrent`包 Java并发包`java.util.concurrent`提供了许多高级并发工具,如`BlockingQueue`、`Semaphore`等,这些工具内部已经实现了高效的等待唤醒机制,并且更加安全易用。对于许多并发场景,直接使用这些工具可能是更好的选择。 例如,使用`ArrayBlockingQueue`可以非常方便地实现上述的生产者-消费者问题,而无需手动编写复杂的等待唤醒逻辑。 ```java ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); // 生产者 new Thread(() -> { try { queue.put(1); // 自动处理等待唤醒逻辑 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); // 消费者 new Thread(() -> { try { System.out.println(queue.take()); // 自动处理等待唤醒逻辑 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); ``` #### 31.7 结论 Guarded Suspension模式提供了一种规范化实现等待唤醒机制的方法,它通过封装Java的`wait()`和`notify()`方法,降低了直接使用这些方法的复杂性和出错率。然而,随着Java并发工具的不断丰富,开发者应当根据具体场景选择合适的并发工具,以提高开发效率和代码质量。无论是直接使用Guarded Suspension模式还是利用`java.util.concurrent`包中的高级工具,理解等待唤醒机制的基本原理都是至关重要的。
上一篇:
30 | 线程本地存储模式:没有共享,就没有伤害
下一篇:
32 | Balking模式:再谈线程安全的单例模式
该分类下的相关小册推荐:
深入理解Java虚拟机
Java语言基础12-网络编程
SpringBoot零基础到实战
Java必知必会-Maven高级
Java语言基础8-Java多线程
Java语言基础5-面向对象初级
Java语言基础2-运算符
Mybatis合辑4-Mybatis缓存机制
Java语言基础7-Java中的异常
Mybatis合辑1-Mybatis基础入门
java源码学习笔记
Java语言基础4-数组详解