并发编程 19—— 显式的Conditon 对象

时间:2023-03-09 15:03:24
并发编程 19—— 显式的Conditon 对象

Java并发编程实践 目录

并发编程 01—— ThreadLocal

并发编程 02—— ConcurrentHashMap

并发编程 03—— 阻塞队列和生产者-消费者模式

并发编程 04—— 闭锁CountDownLatch 与 栅栏CyclicBarrier

并发编程 05—— Callable和Future

并发编程 06—— CompletionService : Executor 和 BlockingQueue

并发编程 07—— 任务取消

并发编程 08—— 任务取消 之 中断

并发编程 09—— 任务取消 之 停止基于线程的服务

并发编程 10—— 任务取消 之 关闭 ExecutorService

并发编程 11—— 任务取消 之 “毒丸”对象

并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性

并发编程 13—— 线程池的使用 之 配置ThreadPoolExecutor 和 饱和策略

并发编程 14—— 线程池 之 整体架构

并发编程 15—— 线程池 之 原理一

并发编程 16—— 线程池 之 原理二

并发编程 17—— Lock

并发编程 18—— 使用内置条件队列实现简单的有界缓存

并发编程 19—— 显式的Conditon 对象

并发编程 20—— AbstractQueuedSynchronizer 深入分析

并发编程 21—— 原子变量和非阻塞同步机制

概述

第1部分  定义

第2部分 实例

参考

第1 部分 定义

Condition 是一种广义的内置条件队列,接口如下:

public interface Condition {
// 造成当前线程在接到信号或被中断之前一直处于等待状态。
void await();
// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
boolean await(long time, TimeUnit unit);
// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
long awaitNanos(long nanosTimeout);
// 造成当前线程在接到信号之前一直处于等待状态。
void awaitUninterruptibly();
// 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。
boolean awaitUntil(Date deadline);
void signal(); // 唤醒一个等待线程
void signalAll(); // 唤醒所有等待线程
}

  一个Condition和一个Lock关联在一起,就像一个条件队列和一个内置锁相关联一样。要创建一个Condition,可以在相关联的Lock上调用Lock.newCondition方法。正如Lock比内置加锁提供了更为丰富的功能,Condition同样比内置条件队列提供了更丰富的功能:在每个锁上可存在多个等待、条件等待可以是可中断的或不可中断的、基于时限的等待,以及公平的或非公平的队列操作。

  与内置条件队列不同的是,对于每个Lock,可以有任意数量的Condition对象。Condition对象继承了相关的Lock对象的公平性,对于公平的锁,线程会依照FIFO顺序从Condition.await中释放。

  下面程序给出了有界缓存的另一种实现,即使用两个Condition,分别为notFull和notEmpty,用于表示“非满”与“非空”两个条件谓词。当缓存为空时,take将阻塞并等待notEmpty,此时put向notEmpty发送信号,可以解除任何在take中阻塞的线程。

 /**
* 14.11 使用显式条件变量的有界缓存
* @ClassName: ConditionBoundedBuffer
* @author xingle
* @param <T>
* @date 2015-2-9 上午11:16:32
*/
public class ConditionBoundedBuffer<T> {
protected final Lock lock = new ReentrantLock();
//条件谓词:notFull (count < items.length)
private final Condition notFull = lock.newCondition();
//条件谓词:notEmpty (count > 0)
private final Condition notEmpty = lock.newCondition();
private static final int BUFFER_SIZE = 100;
@GuardedBy("lock")
private final T[] items = (T[]) new Object[BUFFER_SIZE];
@GuardedBy("lock")
private int tail,head,count; //阻塞并直到:notFull
public void put(T x) throws InterruptedException {
lock.lock();
try{
while(count == items.length)
notFull.await();
items[tail] = x;
if(++tail == items.length)
tail = 0;
++count;
notEmpty.signal();
}finally{
lock.unlock();
}
} //阻塞并直到:notEmpty
public T take() throws InterruptedException{
lock.lock();
try{
while(count==0)
notEmpty.await();
T x = items[head];
items[head] = null;
if(++head == items.length)
head = 0;
--count;
notEmpty.signal();
return x;
}finally{
lock.unlock();
}
} }

  ConditionBoundedBuffer的行为和BoundedBuffer相同,但它对条件队列的使用方式更容易理解——在分析使用多个Condition的类时,比分析一个使用单一内部队列加多个条件队列的类简单得多。通过将两个条件谓词分开并放到两个等待线程集中,Condition使其更容易满足单次通知的需求。signal比singalAll更高效,它能极大地减少在每次缓存操作中发生的上下文切换与锁请求的次数。

第2部分 实例

本示范简单模拟银行帐户的存取款活动,帐户余额大于等于取款金额时允许取款;帐户余额小于1000时允许存款(这与真实业务逻辑不符合,只是技术上需要才如此做的)。

1. 实体Account类

 /**
*
* @ClassName: Account
* @author xingle
* @date 2015-2-9 下午5:54:02
*/
public class Account {
private final Lock lock = new ReentrantLock(); // Condition对象
private final Condition condDeposit = lock.newCondition();
private final Condition condWithdraw = lock.newCondition();
private int balance; public Account(int balance){
this.balance = balance;
} //取钱
public void withdraw(int drawAmount) {
lock.lock();
try {
//如果账户余额不足,则取现方法阻塞
while (balance < drawAmount){
System.out.println("取钱阻塞");
condWithdraw.await();
}
//执行取钱
balance -= drawAmount;
System.out.println(Thread.currentThread().getName() + " 取钱:" + drawAmount + " 账户余额为:"+ balance);
//唤醒存钱线程
condDeposit.signal();
} catch (InterruptedException ex) {
ex.printStackTrace();
} finally {
lock.unlock();
}
} //存钱
public void deposit(int depositAmount){
lock.lock();
try{
//如果账户余额大于1000,存钱方法阻塞
while(balance >1000){
System.out.println("存钱阻塞");
condDeposit.await();
}
balance += depositAmount;
System.out.println(Thread.currentThread().getName() + " 存款:" + depositAmount + " 账户余额为:"+ balance);
//唤醒取钱线程
condWithdraw.signal();
} catch(InterruptedException ex){
ex.printStackTrace();
}
finally{
lock.unlock();
}
}
}

2. 调用类(DepositDrawTest类)

 /**
*
* @ClassName: DepositDrawTest
* @author xingle
* @date 2015-2-10 上午10:38:44
*/
public class DepositDrawTest { public static void main(String[] args) {
// 创建一个账户,初始账户余额为0
Account acct = new Account(0);
new DrawThread("取钱者1", acct, 400).start();
new DrawThread("取钱者2", acct, 800).start();
new DepositThread("存款者甲", acct, 600).start();
new DepositThread("存款者乙", acct, 800).start();
new DepositThread("存款者丙", acct, 400).start(); }
} class DrawThread extends Thread {
// 模拟用户账户
private Account account;
// 每次取数数
private int drawAmount; public DrawThread(String name, Account account, int drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
} public void run() {
for (int i = 0; i < 3; i++) {
account.withdraw(drawAmount);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} class DepositThread extends Thread {
// 模拟用户账户
private Account account;
// 每次存钱数
private int depositAmount; public DepositThread(String name, Account account, int depositAmount) {
super(name);
this.account = account;
this.depositAmount = depositAmount;
} public void run() {
for (int i = 0; i < 3; i++) {
account.deposit(depositAmount);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

执行结果:

并发编程 19—— 显式的Conditon 对象

3. 总结

  1. 如果取款金额大于余额则不让取款,等存款队列继续存钱,余额足够支付时再让取款。
  2. 如果存款过多(大于1000),则存款不让存了,等取款队列把钱取走,余额降低到1000以下时,可以继续存款。
  3. 这样就允许多次连续取款(只要帐户有钱),多次连续存款(余额不能大于1000),而不是存款、取款依次调用。

参考:

1. Java:多线程,使用同步锁(Lock)时利用Condition类实现线程间通信

2. 怎么理解Condition