并发编程 19—— 显式的Conditon 对象
Java并发编程实践 目录
并发编程 04—— 闭锁CountDownLatch 与 栅栏CyclicBarrier
并发编程 06—— CompletionService : Executor 和 BlockingQueue
并发编程 10—— 任务取消 之 关闭 ExecutorService
并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
并发编程 13—— 线程池的使用 之 配置ThreadPoolExecutor 和 饱和策略
并发编程 20—— AbstractQueuedSynchronizer 深入分析
概述
第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();
}
}
}
}
执行结果:
3. 总结
- 如果取款金额大于余额则不让取款,等存款队列继续存钱,余额足够支付时再让取款。
- 如果存款过多(大于1000),则存款不让存了,等取款队列把钱取走,余额降低到1000以下时,可以继续存款。
- 这样就允许多次连续取款(只要帐户有钱),多次连续存款(余额不能大于1000),而不是存款、取款依次调用。
参考:
1. Java:多线程,使用同步锁(Lock)时利用Condition类实现线程间通信
并发编程 19—— 显式的Conditon 对象的更多相关文章
- Java并发编程之显式锁机制
我们之前介绍过synchronized关键字实现程序的原子性操作,它的内部也是一种加锁和解锁机制,是一种声明式的编程方式,我们只需要对方法或者代码块进行声明,Java内部帮我们在调用方法之前和结束时加 ...
- 使用显式的Lock对象取代synchronized关键字进行同步
Java SE5的java.util.concurrent类库还包含有定义在java.util.concurrent.locks中的显式的互斥机制.Lock对象必须被显式地创建.锁定和释放.因此,它与 ...
- 《Java并发编程实战》第三章 对象的共享 读书笔记
一.可见性 什么是可见性? Java线程安全须要防止某个线程正在使用对象状态而还有一个线程在同一时候改动该状态,并且须要确保当一个线程改动了对象的状态后,其它线程能够看到发生的状态变化. 后者就是可见 ...
- 《Java并发编程实战》第四章 对象的组合 读书笔记
一.设计线程安全的类 在设计线程安全类的过程中,须要包括下面三个基本要素: . 找出构成对象状态的全部变量. . 找出约束状态变量的不变性条件. . 建立对象状态的并发訪问管理策略. 分析对象的 ...
- Java编程思想学习笔记-使用显式的Lock对象
若要保证后台线程在trylock()之前运行得到锁,可加“屏障”,如下1,2,3步,而trylock()不管设定时间与否都不会阻塞主线程而是立即返回: //: concurrency/AttemptL ...
- java并发编程读书笔记(1)-- 对象的共享
1. 一些原则 RIM(Remote Method Invocation):远程方法调用 Race Condition:竞态条件 Servlet要满足多个线程的调用,必须是线程安全的 远程对象,即通过 ...
- JAVA并发编程实战---第三章:对象的共享(2)
线程封闭 如果仅仅在单线程内访问数据,就不需要同步,这种技术被称为线程封闭,它是实现线程安全性的最简单的方式之一.当某个对象封闭在一个线程中时,这种方法将自动实现线程安全性,即使被封闭的对象本生不是线 ...
- java并发编程实战:第四章----对象的组合
一.设计线程安全的类 找出构造对象状态的所有变量(若变量为引用类型,还包括引用对象中的域) 约束状态变量的不变性条件 建立对象状态的并发访问管理策略(规定了如何维护线程安全性) 1.收集同步需求(找出 ...
- Java并发编程学习笔记(三)——对象的组合
重要概念: 1.在设计线程安全类的过程中,需要包含以下三个基本要素: (1)找出构成对象状态的所有变量. (2)找出约束状态变量的不变性条件. (3)建立对象状态的并发访问管理策略. 2.
随机推荐
- javascript unit testing
http://www.cnblogs.com/Answer1215/p/4230083.html Good http://developer.51cto.com/art/201506/479127.h ...
- iOS,自动布局autoresizing和auto layout,VFL语言
1.使用autoresizing 2.使用autolayout 3.VFL语言(Visual Format Language:可视化格式语言) 使用autoresizing 点击xib文件,去掉使用a ...
- OleContainer控件介绍
OLEContainer 控件的主要属性 1) AllowInPlace property AllowInPlace:Boolean; 这个属性用于决定启动O ...
- 记录一下我使用的vim的配置文件
还不是很完美: "au BufReadPost * if line("'\"") > 0|if line("'\"") &l ...
- Microsoft Office Excel 不能访问文件及COM无法访问
Microsoft Office Excel 不能访问文件及COM无法访问 Microsoft Office Excel 不能访问文件“*.xls”. 可能的原因有: 1 文件名称或路径不存在. 2 ...
- 淘宝druid数据库连接池
昨天偶然间在@红薯的一篇技术分享中发现了它的身影,从此想到了去看看他到底是什么西,然后在@开源中国上看到了它的功能介绍,心痒难耐 开始了对它的配置测试. 但是第一次启动就发现了一个问题, Tomat报 ...
- Ajax中dataType数据类型
今天项目中使用Ajax向后台保存数据,其中dataType为'json';当请求成功后,没有走success回调,反而走了error:数据库已经成功保存数据了. 后来搞半天才知道原来dataType指 ...
- (8) 深入理解Java Class文件格式(七)
转载:http://blog.csdn.net/zhangjg_blog/article/details/22091529 本专栏列前面的一系列博客, 对Class文件中的一部分数据项进行了介绍. 本 ...
- linux mysql导入导出
linux下导入.导出mysql数据库命令 一.导出数据库用mysqldump命令(注意mysql的安装路径,即此命令的路径):1.导出数据和表结构:mysqldump -u用户名 -p密码 数据库名 ...
- javscript 中的术语和俚语
语言中俚语和方言.在JavaScript中也有一些俚语或者说是术语,看似奇淫巧技,还是有一些用处,有三种语言组件可以来构造术语:强转.逻辑运算符和位变换. 1.强转:在javascript和大部分的语 ...