java多线程(三)

线程通信

当线程在系统内运行时,线程的调度具有一定透明性,程序通常无法准确控制线程的轮换执行,但我们可以通过一些机制来保证线程协调运行。

1. 线程的协调运行
假设系统中有两条线程,分别代表存款这和取钱者。系统要求存款者和取钱者不断的重复存款、取钱的动作,而且要求每当存款者存入取钱者就立即取出,不允许连续存或取。
为实现这种功能,可以借助Object类提供的wait()、notify()和notifyAll()三个方法,这三个方法属于Object类,必须由同步监视器对象调用,可以分成两种情况:

  • 对于使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这三个方法;
  • 对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用这三个方法。

关于这三个方法:

  • wait():导致当线程等待,直到其他线程调用该同步监视器notify()方法或notifyAll()方法来唤醒该线程。wait方法有三种方式,无参数wait是一直等待,有参数的wait等待指定时间后自动苏醒。调用wait()方法的当前线程会释放对同步监视器的锁定。
  • notify():唤醒在此同步监视器上等待的单个线程。若所有线程都在此同步监视器上等待,则会任意选择唤醒其中一个线程。
  • notifyAll():唤醒在此同步监视器上等待的所有线程。

程序中可以设置一个flag,当flag为false时,表明账户没有存款,存款线程可以向下执行,当存款线程执行之后,flag就会设置为true,并调用notify()或者notifyAll()方法唤醒其他线程;当存款者线程进入线程体后,若flag为true,则调用wait方法阻塞该线程。
当flag为true时,表明账户中已经有了存款,则取钱者线程可以向下执行,当取钱者把钱从账户中取出后,flag便设置为false,并调用notify()或notifyAll()方法唤醒其他线程;当取钱者线程进入线程体后,若flag为false就调用wait方法阻塞该线程。

代码如下:

class Account {
    private String accountNo;
    private double balance;
    private boolean flag = false;

    public Account(){}
    public Account(String accountNo, int balance){
        this.accountNo = accountNo;
        this.balance = balance;

    }

    public double getBalance(){
        return this.balance;
    }

    public synchronized void draw(double drawAccount){
        try{
            if(!flag){
                wait();
            }else{
                System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAccount);
                balance -= drawAccount;
                System.out.println("账户余额为:"+balance);
                flag = false;
                notifyAll();
            }
        }
        catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }

    public synchronized void deposit(double depositAccount){
        try{
            if (flag){
                wait();
            }else{
                System.out.println(Thread.currentThread().getName()+" 存款:"+depositAccount);
                balance += depositAccount;
                System.out.println("账户余额为:"+balance);
                flag = true;
                notifyAll();
            }

        }
        catch(InterruptedException ex){
            ex.printStackTrace();
        }
    }
}


class DrawThread extends Thread{
    private Account account;
    private double drawAmount;
    DrawThread(String name, Account account, double drawAmount){
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }

    public void run(){
        for(int i = 0; i < 100; i++){
            account.draw(drawAmount);
        }
    }
}

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;
    }

    public void run(){
        for(int i = 0; i < 100; i++){
            account.deposit(depositAmount);
        }
    }
}

public class TestDraw{
    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();
    }
}

最后的结果:
线程
结果显示程序最后被阻塞无法继续向下执行,这是因为3个存款者线程共有300次存款操作,但是1个取钱者只有100次取钱操作,所以程序最后被阻塞。
注意:阻塞不是死锁,这种情况取钱者线程已经执行结束,而存款者线程在等待其他线程来取钱,并不是等待其他线程释放同步监视器

2. 使用条件变量控制协调
若程序使用Lock对象来保证同步,则系统不存在隐式的同步监视器对象,不能使用wait、notify、notifyAll方法来协调进程的运行。
当使用Lock对象保证同步时,Java提供了Condition类来保持协调,使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Condition对象可以唤醒其他处于等到的线程。
Condition实例实质上被绑定在一个Lock对象上,要获得特定Lock实例的Condition实例,调用Lock对象newCondition()方法即可。Condition替代了同步监视器的功能。

  • await():类似于wait方法,可以让当前线程等待,直到其他线程调用该Condition的signal()或signalAll()方法来唤醒该线程
  • signal():唤醒在此Lock对象上等待的单个线程,若所有线程都在等待,则随机选择唤醒一个线程
  • signalAll():唤醒在此Lock对象上等待的所有线程
    定义Condition的语句如下:
private final Lock lock = new ReetrantLock();
private final Condition cond = lock.newCondition();

3. 使用管道流
前面介绍的两种方式都是线程之间协调运行的控制策略,若需要在两条线程之间进行更多信息交互,则需要用管道流进行通信。
管道流有三种存在形式:PipedInputStream和PipedOutputStream、PipedReader和PipedWriter以及Pipe.SinkChannel和Pipe.SourceChannel,它们分别是管道字节流、管道字符流和新IO的管道Channel。
使用管道流实现多线程通信按如下步骤:

  1. 使用new操作符分别创建管道输入流和管道输出流
  2. 使用管道输入流或管道输出流的connect方法把两个输入流和输出流连接起来
  3. 将管道输入流、管道输出流分别传入两个线程
  4. 两个线程可以分别依赖各自的管道输入流和管道输出流进行通信
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值