(六)多线程:线程通信

 当线程在操作系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但Java也提供了一些机制来保证线程协调运行。

1.传统的线程通信

 可以借助Object类提供的wait(),notify(),和notifyAll()三个方法,这三个方法不属于Thread类,而是属于Object类。但这三个方法必须由同步监视器对象来调用,这可分成以下两种情况。

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

关于这三个方法的解释如下:

  • wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。该wait()方法有三种形式——无时间参数wait(一直等待,直到其他线程通知),带毫秒参数的wait()和带毫秒,毫微秒的wait()(这两种方法都是等待指定时间后自动苏醒)。调用wait()方法的当前线程会释放对该同步监视器的锁定。
  • notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在同步监视器上等待,则会选择唤醒其中一个线程。选择时任意性的。只有当前线程放弃对该同步监视器的锁定后(使用wait()方法),才可以执行被唤醒的线程。
  • notifyAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。

代码示例场景:三个存钱线程,一个取钱线程,要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱。不允许存款者连续两次存钱,也不允许取钱者连续两次取钱。
示例代码:

public class Account {

    private String account;

    private Double balance;

    //标识账户中是否有存款标志
    private boolean flag;


    public Account(String account, double balance, boolean flag) {
        this.account = account;
        this.balance = balance;
        this.flag = flag;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public Double getBalance() {
        return balance;
    }


    //取钱操作
    public synchronized void take(double money) {
        try {
            if (!flag) {

                wait();

            } else {
                double befroe_balance = this.balance;
                this.balance = this.balance - money;
                System.out.println("线程:" + Thread.currentThread().getName() + "取钱:" + money + "账户取前余额:" + befroe_balance + "账户取后余额:" + this.balance);
                flag = false;
                //唤醒其他线程
                notifyAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //存钱操作
    public synchronized void deposit(double money) {
        try {
            if (flag) {

                wait();

            } else {
                double befroe_balance = this.balance;
                this.balance = this.balance + money;
                System.out.println("线程:" + Thread.currentThread().getName() + "存钱:" + money + "账户存前余额:" + befroe_balance + "账户存后余额:" + this.balance);
                flag = true;
                notifyAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class DepositThread extends Thread {

    private Account account;

    private double deposit_money;

    public DepositThread(String t_name, Account account, double deposit_money) {
        super(t_name);
        this.account = account;
        this.deposit_money = deposit_money;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            account.deposit(deposit_money);
        }
    }
}
public class TakeThread extends Thread {

    private Account account;

    private double take_money;

    public TakeThread(String t_name, Account account, double take_money) {
        super(t_name);
        this.account = account;
        this.take_money = take_money;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            account.take(take_money);
        }
    }
}

public class TakeTest {

    public static void main(String[] args) {
        Account account = new Account("123456", 0, false);

        new TakeThread("取钱者A", account, 800).start();

        new DepositThread("存钱者B", account, 800).start();

        new DepositThread("存钱者C", account, 800).start();

        new DepositThread("存钱者D", account, 800).start();
    }
}

运行结果:

线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者D存钱:800.0账户存前余额:0.0账户存后余额:800.0
线程:取钱者A取钱:800.0账户取前余额:800.0账户取后余额:0.0
线程:存钱者B存钱:800.0账户存前余额:0.0账户存后余额:800.0

注意:运行结果所示阻塞并不是死锁,对于这种情况,取钱者线程已经执行结束,而存款线程只是等待其他线程来取钱而已,并不是等待其他线程释放同步监视器,不要把死锁和程序阻塞等同起来!

2.使用Condition控制线程通信

 如果程序不使用synchronized关键字来保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐试的同步监视器,也就不能使用wait(),notify(),notifyAll()方法进行线程通信了。
 当使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Conditon对象也可以唤醒其他处于等待的线程。
 Condition将同步监视器方法(wait(),notify(),notifyAll())分解成截然不同的对象,以便通过将这些对象与Lock对象组合使用,为每个对象提供多个等待集(wait-set)。在这种情况下,Lock替代了同步方法或同步代码块,Condition替代了同步监视器的功能。
 Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可。Condition类提供了如下三个方法。

  • await():类似于隐式同步监视器上的wait()方法,导致当前线程等待,直到其他线程调用该Condition的signal()方法或signal()方法或signalAll()方法来唤醒该线程。
  • signal():唤醒在此Lock对象上等待的单个线程。如果所有线程都在该Lock对象上等待,则会选择唤醒其中一个线程。选择式任意性的。只有当前线程放弃对该Lock对象的锁定后(使用await()方法),才可以执行被唤醒的线程。
  • signAll():唤醒在此Lock对象上等待的所有线程,只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程。

3.使用阻塞队列(BlockingQueue)控制线程通信

 Java5提供了一个BlockingQueue也是Queue的子接口,但它的主要用途并不是作为容器,而是作为线程同步的工具。BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。
 程序的两个线程通过交替向BlockingQueue中放入元素,取出元素,即可很好地控制线程的通信。
BlockingQueue提供如下两个支持阻塞的方法:

  • put(E e):尝试把E元素放入BlockingQueue中,如果该队列元素已满,则阻塞该线程。
  • take():尝试从BlockingQueue的头部取出元素,如果该队列的元素已空,则阻塞该线程。

文章内容均取自《疯狂Java讲义-李刚》一书中多线程章节。截取重要知识点作为笔记记录,方便自己回顾。

猜你喜欢

转载自www.cnblogs.com/everyingo/p/12812184.html