Java 中的受保护挂起模式:确保关键部分的安全并发
大约 3 分钟
也称为
- 条件块
- 挂起执行
受保护挂起设计模式的意图
受保护挂起模式在 Java 设计模式中至关重要,用于管理需要锁定和条件才能执行的操作。它通过允许线程高效地等待正确条件来优化并发控制。
带实际示例的受保护挂起模式的详细解释
实际示例
在拼车服务中可以看到受保护挂起模式的实际示例。在这个系统中,乘客会等待汽车可用,从而确保高效地使用资源,而无需持续检查。当乘客请求乘车时,该请求会暂停,直到有司机可用。该系统会监控司机是否可用,一旦司机准备好接送新的乘客,该系统会通知等待的乘客并恢复乘车请求过程。这确保乘客不会持续检查是否有可用司机,并且司机根据其可用情况与乘客进行有效匹配。
简单来说
当一个线程等待另一个线程执行的结果时,会使用受保护挂起模式。
维基百科说
在并发编程中,受保护挂起模式管理需要锁和前提条件的操作,延迟执行,直到满足前提条件。
Java 中受保护挂起模式的编程示例
Java 中的 GuardedQueue
类展示了使用受保护挂起模式的并发编程。它包括同步方法来管理线程管理和同步,展示了线程如何等待正确条件才能执行。
GuardedQueue
类通过封装一个队列并提供两个同步方法 get
和 put
来演示受保护挂起模式。get
方法在队列为空时等待,而 put
方法向队列添加一个项并通知任何等待的线程。
@Slf4j
public class GuardedQueue {
private final Queue<Integer> sourceList = new LinkedList<>();
// Synchronized get method waits until the queue is not empty
public synchronized Integer get() {
while (sourceList.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
LOGGER.error("Error occurred: ", e);
}
}
return sourceList.poll();
}
// Synchronized put method adds an item to the queue and notifies waiting threads
public synchronized void put(Integer e) {
sourceList.add(e);
notify();
}
}
get
:此方法在sourceList
为空时等待。当添加一个项并调用notify
时,等待的线程会醒来继续执行并检索该项。put
:此方法向队列添加一个项,并调用notify
来唤醒在get
方法中暂停的任何等待线程。
以下是驱动示例的 App
类
public class App {
public static void main(String[] args) {
GuardedQueue guardedQueue = new GuardedQueue();
ExecutorService executorService = Executors.newFixedThreadPool(3);
// Thread to get from the guardedQueue
executorService.execute(() -> {
Integer item = guardedQueue.get();
LOGGER.info("Retrieved: " + item);
});
// Simulating some delay before putting an item
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
LOGGER.error("Error occurred: ", e);
}
// Thread to put an item into the guardedQueue
executorService.execute(() -> {
guardedQueue.put(20);
LOGGER.info("Item added to queue");
});
executorService.shutdown();
try {
executorService.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.error("Error occurred: ", e);
}
}
}
ExecutorService
用于管理线程池。- 第一个线程尝试从
GuardedQueue
中检索一个项,并等待,因为队列最初是空的。 - 延迟 2 秒后,第二个线程向队列添加一个项,唤醒第一个线程。
- 然后,第一个线程检索该项并记录它。
执行结果
19:22:58.984 [pool-1-thread-1] INFO com.iluwatar.guarded.suspension.GuardedQueue -- waiting
19:23:00.993 [pool-1-thread-2] INFO com.iluwatar.guarded.suspension.GuardedQueue -- putting
19:23:00.994 [pool-1-thread-2] INFO com.iluwatar.guarded.suspension.GuardedQueue -- notifying
19:23:00.994 [pool-1-thread-1] INFO com.iluwatar.guarded.suspension.GuardedQueue -- getting
19:23:00.994 [pool-1-thread-1] INFO com.iluwatar.guarded.suspension.GuardedQueue -- Retrieved: 20
- 日志输出显示了事件顺序:第一个线程等待,第二个线程放入一个项,然后第一个线程检索该项。这演示了受保护挂起模式的实际应用。
何时在 Java 中使用受保护挂起模式
对于需要线程等待特定条件的场景,此模式非常理想,它可以促进有效的并发控制并减少繁忙等待的开销。
Java 中受保护挂起模式的实际应用
- 等待客户端请求的网络服务器。
- 生产者-消费者场景,其中消费者必须等待生产者提供数据。
- 事件驱动的应用程序,其中操作只有在发生特定事件后才会触发。
受保护挂起模式的优缺点
优点
- 通过防止繁忙等待来减少 CPU 消耗。
- 通过将操作同步到必要条件或资源的可用性来提高系统响应能力。
权衡
- 实现的复杂性,尤其是在需要管理多个条件时。
- 如果管理不当,可能会导致死锁。
相关的 Java 设计模式
- 监视器:这两种模式都根据条件管理线程的同步。受保护挂起专门处理挂起线程,直到满足条件,而监视器对象封装了条件和互斥处理。
- 生产者-消费者:通常使用受保护挂起模式来有效地处理等待的消费者和生产者。
- 退却:与受保护挂起类似,退却用于当线程检查条件,并且只有在条件有利时才继续执行;如果不是,则立即返回或退出。此模式通过管理基于立即条件检查的操作,而不是等待,来补充受保护挂起。