zoukankan      html  css  js  c++  java
  • java多线程回顾4:线程通信

    1、线程的协调运行

    线程的协调运行有一个经典案例,即生产者和消费者问题。

    假设有一个货架,生产者往货架上放货物,消费者从货架上取货物。

    为了方便讲解,制定一个规则,生产者每放上一个货物,消费者就得取走一个货物。不允许连续放两次,也不允许连续取两次。

    为了实现这个功能,可以使用wait()notify()notifyAll()三个方法。注意,这三个方法不属于Thread类,而是属于Object类,且必须由同步监视器对象调用。详细用法如下:

    <![if !supportLists]>Ø  <![endif]>对于使用synchronized修饰的同步方法,因为默认实例(this)就是同步监视器对象,所以可以在同步方法中直接调用这三个方法。

    <![if !supportLists]>Ø  <![endif]>对于使用synchronized修饰的同步代码块,同步监视器对象是synchronized后括号里的对象,必须使用该对象调用这三个方法

    这三个方法的作用如下:

    <![if !supportLists]>Ø  <![endif]>wait():使当前线程等待,直到其他线程调用该同步监视器的notify()notifyAll()来唤醒该线程。调用wait()的当前线程会释放对该同步监视器的锁定。

    <![if !supportLists]>Ø  <![endif]>notify():唤醒在此同步监视器上的单个线程,如果有多个线程在等待,则随机唤醒一个。被唤醒的线程会在当前线程释放对同步监视器的锁定后开始运行。

    <![if !supportLists]>Ø  <![endif]>notifyAll():唤醒在此同步监视器上的所有线程,其余和notify()相同。

    货架代码如下:

    publicclass Shelf {

        //是否有货物的标志,true:有;false:没有

        privatebooleanflag;

       

        //放入货物的方法

        publicsynchronizedvoid product(){

           //flag为假时表示没有货物,此时可放入货物

           if(!flag){

               flag = true;

               System.out.println(Thread.currentThread().getName()+"放入了货物。");

               notifyAll();

           }else{

               try {

                  wait();

               } catch (InterruptedException e) {

                  e.printStackTrace();

               }

           }

        }

        //取走货物的方法

        publicsynchronizedvoid custom(){

           //flag为真时表示有货物,此时可取走货物

           if (flag) {

               flag = false;

               System.out.println(Thread.currentThread().getName()+"取走了货物。");

               notifyAll();

           }else{

               try {

                  wait();

               } catch (InterruptedException e) {

                  e.printStackTrace();

               }

           }

        }

    }

    生产者线程代码如下:

    publicclass ProducerThread implements Runnable{

        private Shelf shelf;

        public ProducerThread(Shelf shelf){

           this.shelf = shelf;

        }

        @Override

        publicvoid run() {

           //放十次货物

           for (int i = 0; i < 10; i++) {

               shelf.product();

           }

        }

    }

    消费者线程代码如下:

    publicclass CustomerThread implements Runnable{

        private Shelf shelf;

        public CustomerThread(Shelf shelf){

           this.shelf = shelf;

        }

        @Override

        publicvoid run() {

           //取十次货物

           for (int i = 0; i < 10; i++) {

               shelf.custom();

           }

        }

    }

    测试代码如下:

    publicclass TestShelf {

        publicstaticvoid main(String[] args) {

           Shelf shelf = new Shelf();

           ProducerThread producerThread = new ProducerThread(shelf);

           CustomerThread customerThread = new CustomerThread(shelf);

          

           new Thread(producerThread,"生产者1").start();

           new Thread(producerThread,"生产者2").start();

           new Thread(customerThread,"消费者").start();

        }

    }

    运行结果如下:

    生产者1放入了货物。

    消费者取走了货物。

    生产者1放入了货物。

    消费者取走了货物。

    生产者2放入了货物。

    消费者取走了货物。

    生产者1放入了货物。

    消费者取走了货物。

    生产者1放入了货物。

    消费者取走了货物。

    生产者2放入了货物。

    消费者取走了货物。

    生产者2放入了货物。

    消费者取走了货物。

    生产者2放入了货物。

    上例中启动了两个生产者一个消费者,可见生产和消费是交替进行的,满足生产者每放上一个货物,消费者就得取走一个货物的规则。

    最后程序将阻塞,因为消费者比较少,生产者最后只能一直等消费者来取走货物。同时消费者取货物的次数也小于10次,因为有时取货时没货了。

    2、使用条件变量协调运行

    如果不使用synchronized,而是使用Lock对象来保证同步,则不能使用wait()notify()notifyAll()这三个方法。此时可使用Condition类来实现协作。

    Condition实例必须从Lock实例中获得,也有三个方法,如下:

    <![if !supportLists]>Ø  <![endif]>await():和wait()类似,使当前线程等待,直到被唤醒。相比wait()await()有更多的变体,这个以后再说。

    <![if !supportLists]>Ø  <![endif]>signal():和notify()类似,唤醒当前Lock对象上的单条线程。

    <![if !supportLists]>Ø  <![endif]>signalAll()notifyAll()类似,唤醒当前Lock对象上的所有线程。

    生产者线程、消费者线程、测试代码和测试结果的代码跟上一节完全一样,这里只放出货架的代码。

    货架的代码:

    publicclass Shelf {

        //是否有货物的标志,true:有;false:没有

        privatebooleanflag;

        //定义Lock对象

        privatefinal Lock lock = new ReentrantLock();

        //获取Lock对象对应的Condition对象

        privatefinal Condition condition= lock.newCondition();

       

        //放入货物的方法

        publicvoid product(){

           lock.lock();

           try {

               //flag为假时表示没有货物,此时可放入货物

               if(!flag){

                  flag = true;

                  System.out.println(Thread.currentThread().getName()+"放入了货物。");

                  condition.signalAll();

               }else{

                  condition.await();

               }

           } catch (Exception e) {

               e.printStackTrace();

           }finally{

               lock.unlock();

           }

          

          

        }

        //取走货物的方法

        publicsynchronizedvoid custom(){

           lock.lock();

           try {

               //flag为真时表示有货物,此时可取走货物

               if (flag) {

                  flag = false;

                  System.out.println(Thread.currentThread().getName()+"取走了货物。");

                  condition.signalAll();

               }else{

                  condition.await();

               }

           } catch (Exception e) {

               // TODO: handle exception

               e.printStackTrace();

           }finally{

               lock.unlock();

           }

        }

    }

    3、使用管道流通信

    线程之间使用管道流通信并不是最好的方式,通常推荐使用共享资源的方式交换信息。所以本节内容了解一下就好。

    管道流有三种形式,分别是管道字节流(PipedInputStreamPipedOutputStream)、管道字符流(PipedReaderPipedWriter)和新IO的管道ChannelPipe.SinkChannelPipe.SourceChannel)。

    使用步骤如下:

    <![if !supportLists]>Ø  <![endif]>创建输入管道输入流和管道输出流对象。

    <![if !supportLists]>Ø  <![endif]>使用管道输入流或管道输出流的connect方法把输入流和输出流连接起来。

    <![if !supportLists]>Ø  <![endif]>将管道输入流、管道输出流分别传入两个线程

    <![if !supportLists]>Ø  <![endif]>线程使用各自的管道流通信

    代码就不贴了

     

     





  • 相关阅读:
    LOJ #2196「SDOI2014」LIS
    LOJ#2249 Luogu P2305「NOI2014」购票
    LOJ #6268 分拆数
    Luogu P3700「CQOI2017」小Q的表格
    LOJ #2527 Luogu P4491「HAOI2018」染色
    knockout 学习使用笔记-----event绑定传参ko属性
    bootstrap select 学习使用笔记-------选中赋值及change监听丢失
    knockout 学习使用笔记------绑定值时赋值失败
    js 数组判断是否包含某元素 或 数组去重
    bootstrap-select 使用笔记 设置选中值及手动刷新
  • 原文地址:https://www.cnblogs.com/bailiyi/p/5310639.html
Copyright © 2011-2022 走看看