通过保证在临界区上多个线程的相互排斥,线程间可以完全避免竞争状态的发生,但是有时候还是需要线程之间的相互协作。使用条件(Condition)便于线程间通信。一个线程可以指定在某种条件下该做什么。标间是通过调用Lock对象的newCoditionn()方法来实现线程之间的相互通信的。
一旦创建一个条件,就可使用await()、signal()、signalAll()方法来实现线程间通信。await()方法可以让当前线程都处于等待状态,知道条件放生。signal()方法唤醒一个等待的线程,而signalAll()方法唤醒所有等待线程。
假设创建并启动两个任务,一个用来向账户存款,另一个从同一个账户取款。当取款数额大于账户余额的时,取款线程必须等待。不管什么时候,只要向账户新存了一笔资金,存款线程必须通知提款线程重新尝试。如果余额仍为达到取款数额、提款线程必须继续等待新的存款。
为了同步这些操作,使用一个由条件的锁newDeposit(即增加到账户的新存款)。如果余额小于取款数额,提款任务将等待newDeposit条件。当存款任务给账户增加资金时,存款任务唤醒等待中的提款任务在次尝试。
看下面的例子
package LianXi;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class ThreadCooperation{
private static Account _account = new Account();
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(new WithdraoTask());
executor.execute(new AddMoneyTask());
}
//执行任务想账户中加钱
public static class AddMoneyTask implements Runnable{
@Override
public void run(){
while(true){
_account.deposit((int)(Math.random()*1000));
}
}
}
//取款任务
public static class WithdraoTask implements Runnable{
@Override
public void run(){
while(true){
_account.withDraw((int)(Math.random()*5000));
}
}
}
//账户
public static class Account{
private static Lock _lock = new ReentrantLock(true); //创建一个锁
private static Condition newDeposit = _lock.newCondition(); // 创建一个条件
private int _moneyCount=0;
public int getMoneyCount(){
return _moneyCount;
}
public void deposit(int amount){
_lock.lock(); //获得锁
int newMoneyCount = getMoneyCount()+amount;
try{
_moneyCount=newMoneyCount;
System.out.println("\n新存入了$"+amount+_account.getAccountInfor());
newDeposit.signalAll();
Thread.sleep(1000);
} catch(InterruptedException ex) {
} finally {
_lock.unlock();
}
}
public void withDraw(int amount){
_lock.lock();
try{
while(getMoneyCount()<amount){
System.out.println("\n"+_account.getAccountInfor()+" 无法去除¥"+amount +",余额不足");
newDeposit.await();
}
_moneyCount-=amount;
Thread.sleep(1000);
System.out.println(" \n取出了¥"+amount+" ,"+ _account.getAccountInfor());
} catch(InterruptedException ex){
ex.printStackTrace();
} finally {
_lock.unlock();
}
}
public String getAccountInfor(){
return "当前账户的余额为:¥"+getMoneyCount();
}
}
}
运行结果:
-
需要注意的地方:1、在取款任务中使用Wile判断,避免永久等待。因为当存款任务调用signalAll()的时候while条件依然为true 。如要再次判断余额和要取的数额,以确定是否可以取款。
2、一旦线程调用雕件的await()方法,进会进入等待状态,如果忘记调用signal 或者signalAll() 就回进入永久等待状态。
3、条件由Lock对象创建,为了嗲用任务方法(如:await。signal 、signalAll),必须首先获得锁。,如果没有获得锁,就调用这些方法会排除 IIlegalMonitorStateException异常。
锁和条件java5 引入的内容,在java 5 之前,线程通信是使用对象的内容见识起编程实现的。锁和条件与内置监视器相比是非常强大而且灵活的。
监视器(monitor)是一个相互作用切具备同步能力的对象,监视器中的一个时间点上,只能有一个线程执行一个方法。线程通过获取监视器上的锁进入监视器,并且通过在方法或块块上使用synchronized关键字来实现。在执行同步方法或块之前,线程必须获取锁。如果条件不适合线程在监视器内继续执行,线程可能在监视器中等待。可以对监视器对象调用wait()方法来释放锁,这样其它的一些监视器中的线程就可以获取它,也就有可能改变监视器的状态。当条件合适时,另一个线程可以调用notiry()或者notifyAll()房东发来说通知一个或多有的等待线程重新获取锁,并且恢复执行。
wait()、notify()、notifyAll*()方法必须在这些方法的接收对象的同步方法或同步块中调用,否则,就回出现IIlegalMonitorStateException异常。
可以将上面的程序修改成为moniitor版本。
当调用wait()方法时,它中止线程的同事释放对象的锁,当线程被通知之后重新启动时,锁就被重新自动获取。对象上的wait()、nitify()、notifyAll方法类似于状态撒谎能够的 await()、signal()和aignalAll()方法。
题外话。PowerDesigner 、画图工具、qq截屏还真是不错,配合使用
,越来越上手了。