java多线程(三)

xiaoxiao2021-02-28  78

线程通信

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

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。 使用管道流实现多线程通信按如下步骤:

使用new操作符分别创建管道输入流和管道输出流使用管道输入流或管道输出流的connect方法把两个输入流和输出流连接起来将管道输入流、管道输出流分别传入两个线程两个线程可以分别依赖各自的管道输入流和管道输出流进行通信
转载请注明原文地址: https://www.6miu.com/read-78313.html

最新回复(0)