线程通信:
程序不能控制线程的轮换执行, 但可以通过一些机制保证线程协调运行.
传统的线程通信
Object类提供了wait(), notify()和notifyAll()三个方法实现线程通信, 它们不属于Thread类.
1. wait(): 导致当前线程等待, 直到其他线程调用该同步监视器的notify()或notifyAll()方法来唤醒该线程(也可以指定等待时间). * 调用wait()方法会释放当前线程对同步监视器的锁定.
2. notify(): 唤醒在此同步监视器上等待的单个线程. 如果多个线程都是在此同步监视器上等待, 则随机唤醒一个. 只有当前线程放弃对该监视器的锁定后(用wait()方法), 才可以执行被唤醒的线程.
3. notifyAll(): 唤醒在此同步监视器上等待的所有个线程. 只有当前线程放弃对该监视器的锁定后(用wait()方法), 才可以执行被唤醒的线程.
wait(), notify()和notifyAll()方法由同步监视器对象来调用:
**对synchronized修饰的同步方法, 因为默认的实例(this)是同步监视器本身, 所以可以直接使用这三个方法.
**对synchronized修饰的同步代码块,同步监视器是synchronized括号后的对象,所以必须使用该对象调用者三个方法。
程序实例:
假设现在系统中有两个线程,这两个线程分别代表存款者和取款者,现在假设要求系统有一种特殊的系统,系统要求存款者和取款者不断地重复存钱 取钱的动作,而且要求每当存款者存钱后,取款者立即取出钱。
**程序中可以通过一个旗标来表示账户中是否有存款,当旗标为false时,表明账户中没有存款,存款者线程可以向下执行,当存款者将钱存入账户后,将旗标设为true,并调用notify()或notifyAll()方法来唤醒其他线程,当存款者进入线程执行体后,如果旗标为true就调用wait()方法让其等待。
**当旗标为true时,表明账户中有存款,取款者线程可以向下执行,当取款者把钱取出账户后,将旗标设为false,并调用notify()或notifyAll()方法来唤醒其他线程,当取款者进入线程执行体后,如果旗标为false就调用wait()方法让其等待。
**本程序为Account类提供draw()和deposit()两个方法,分别对应账户的存钱和取钱操作,因为这两个方法可能需要并发修改Account类的balance成员变量的值,所以这两个方法都使用synchronized修饰成同步方法,除此之外,这两个方法还使用了wait() notifyAll()来控制线程的协作。
package com.bank;
public class Account {
//封装账户编号、账户余额的两个成员变量
private String accountNo;
private double balance;
//标识账户中是否有存款的旗标
private boolean flag=false;
public Account(){
}
//构造器
public Account(String accountNo,double balance){
this.accountNo=accountNo;
this.balance=balance;
}
public String getAccountNo() {
return this.accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
//账户余额不允许随便修改,所以只为balance提供getter方法
public double getBalance() {
return this.balance;
}
public synchronized void draw(double drawAmount){
//如果flag为假,表明账户中还没有钱存入进去,取钱方法阻塞
try {
if(! flag){
wait();
}
else{
//执行取钱操作
System.out.println(Thread.currentThread().getName()+"取钱:"+drawAmount);
balance=balance-drawAmount;
System.out.println("余额为:"+balance);
//将标识账户已有存款旗标设为false
flag= false;
//唤醒其他线程
notifyAll();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized void deposit(double depositAmount){
//如果flag为真,表明账户有钱,存钱方法阻塞
try{
if(flag)
{
wait();
}
//执行存款操作
else{
System.out.println(Thread.currentThread().getName()+"存款"+depositAmount);
balance=balance+depositAmount;
System.out.println("账户余额为:"+balance);
//将标识账户已有存款的旗标设为true
flag=true;
//唤醒其他线程
notifyAll();
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((accountNo == null) ? 0 : accountNo.hashCode());
long temp;
temp = Double.doubleToLongBits(balance);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Account other = (Account) obj;
if (accountNo == null) {
if (other.accountNo != null)
return false;
} else if (!accountNo.equals(other.accountNo))
return false;
if (Double.doubleToLongBits(balance) != Double.doubleToLongBits(other.balance))
return false;
return true;
}
}
程序中线程循环100次重复存款,而取钱线程重复100次取钱,存款者线程和取款者线程分别调用Account对象的deposit()和draw()方法来实现
package com.bank;
public class DrawThread extends Thread{
//模拟用户账目
private Account account;
//当前取钱线程需要的钱数
private double drawAmount;
public DrawThread(String name,Account account,double drawAmount){
super(name);
this.account=account;
this.drawAmount=drawAmount;
}
//重复100次取钱的操作
public void run(){
for (int i = 0; i < 100; i++) {
account.draw(drawAmount);
}
}
}
package com.bank;
public class DepositThread extends Thread {
//模拟用户账目
private Account account;
//当前存钱线程需要的钱数
private double depositAmount;
public DepositThread(String name,Account account,double depositAmount){
super(name);
this.account=account;
this.depositAmount=depositAmount;
}
//重复100次存钱的操作
public void run(){
for (int i = 0; i < 100; i++) {
account.deposit(depositAmount);
}
}
}
主程序可以启动任意多个存款线程和取款线程。可以看到所有的取钱线程必须等到存钱线程存完钱之后才可以向下执行,而存钱线程也必须等取钱线程取钱后才可以向下执行。
package com.bank;
public class DrawTest {
public static void main(String[] args) {
//创建账户
Account acct =new Account("1234567",0);
new DrawThread("取钱者", acct, 800).start();
new DepositThread("存钱者1", acct, 800).start();
new DepositThread("存钱者2", acct, 800).start();
new DepositThread("存钱者3", acct, 800).start();
}
}
//结果如下:
存钱者1存款800.0
账户余额为:800.0
取钱者取钱:800.0
余额为:0.0
存钱者3存款800.0
账户余额为:800.0
取钱者取钱:800.0
余额为:0.0
存钱者1存款800.0
账户余额为:800.0
取钱者取钱:800.0
余额为:0.0
存钱者3存款800.0
账户余额为:800.0
取钱者取钱:800.0
余额为:0.0
存钱者1存款800.0
账户余额为:800.0
取钱者取钱:800.0
余额为:0.0