zoukankan      html  css  js  c++  java
  • (六)多线程:线程通信

     当线程在操作系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但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讲义-李刚》一书中多线程章节。截取重要知识点作为笔记记录,方便自己回顾。

  • 相关阅读:
    <<一线架构师实践指南>>读书笔记之二PA阶段
    【读书笔记】简约至上交互设计四策略第4章 删除
    大数据量简单数据查询设计思考
    识别项目干系人
    【读书笔记】简约至上交互设计四策略第3章 简约四策略
    【读书笔记】简约至上交互设计四策略第2章 明确认识
    【读书笔记】简约至上交互设计四策略第1章 话说简单
    采购管理计划
    项目管理整体的一些基本概念1
    【读书笔记】简约至上交互设计四策略第5章 组织
  • 原文地址:https://www.cnblogs.com/everyingo/p/12812184.html
Copyright © 2011-2022 走看看