zoukankan      html  css  js  c++  java
  • java并发之线程间通信协作

      在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作。比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。

      Java中线程通信协作的最常见的两种方式:

      一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

      二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

      线程间直接的数据交换:

      三.通过管道进行线程间通信:1)字节流;2)字符流   

    一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

    wait()、notify()和notifyAll()是Object类中的方法:

     1 /**
     2  * Wakes up a single thread that is waiting on this object's
     3  * monitor. If any threads are waiting on this object, one of them
     4  * is chosen to be awakened. The choice is arbitrary and occurs at
     5  * the discretion of the implementation. A thread waits on an object's
     6  * monitor by calling one of the wait methods
     7  */
     8 public final native void notify();
     9  
    10 /**
    11  * Wakes up all threads that are waiting on this object's monitor. A
    12  * thread waits on an object's monitor by calling one of the
    13  * wait methods.
    14  */
    15 public final native void notifyAll();
    16  
    17 /**
    18  * Causes the current thread to wait until either another thread invokes the
    19  * {@link java.lang.Object#notify()} method or the
    20  * {@link java.lang.Object#notifyAll()} method for this object, or a
    21  * specified amount of time has elapsed.
    22  * <p>
    23  * The current thread must own this object's monitor.
    24  */
    25 public final native void wait(long timeout) throws InterruptedException;
    View Code

    从这三个方法的文字描述可以知道以下几点信息:

      1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

      2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)

      3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程

      4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程

      有朋友可能会有疑问:为何这三个不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。

      上面已经提到,如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁)因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)如果当前线程没有这个对象的锁就调用wait()方法,则会抛出IllegalMonitorStateException.

       调用某个对象的wait()方法,相当于让当前线程交出(释放)此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);  

      notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。 同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

      nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。

      这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。

       举个简单的例子:假如有三个线程Thread1、Thread2和Thread3都在等待对象objectA的monitor,此时Thread4拥有对象objectA的monitor,当在Thread4中调用objectA.notify()方法之后,Thread1、Thread2和Thread3只有一个能被唤醒。注意,被唤醒不等于立刻就获取了objectA的monitor。假若在Thread4中调用objectA.notifyAll()方法,则Thread1、Thread2和Thread3三个线程都会被唤醒至于哪个线程接下来能够获取到objectA的monitor就具体依赖于操作系统的调度了

      上面尤其要注意一点,一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行

     一个生产者一个消费者

    一个对象,作为锁(利用该对象的monitor)

    1 package com.jp.oneone;
    2 
    3 public class ValueObject {
    4 
    5     public static String value = "";
    6 
    7 }

    生产者:

     1 package com.jp.oneone;
     2 
     3 //生产者
     4 public class P extends Thread{
     5 
     6     private String lock;
     7 
     8     public P(String lock) {
     9         super();
    10         this.lock = lock;
    11     }
    12     
    13     @Override
    14     public void run() {
    15         while (true) {
    16             try {
    17                 synchronized (lock) { //当前线程必须获得锁才可以进行下面的操作
    18                     if (!ValueObject.value.equals("")) {//如果Value不为空,说明字符串还没被消费,所以调用wait方法,把当前线程(生成线程)阻塞
    19                         lock.wait();
    20                     }
    21                     String value = System.currentTimeMillis() + "_"
    22                             + System.nanoTime();
    23                     System.out.println("set的值是" + value);
    24                     ValueObject.value = value;//为空的话,则生成
    25                     lock.notify();//生成完就唤醒等待该对象锁的线程,(这里只有一个消费者等这个锁,所以就是唤醒的它)
    26                 }
    27 
    28             } catch (InterruptedException e) {
    29                 e.printStackTrace();
    30             }
    31         }
    32     }
    33 }

    消费者:

    package com.jp.oneone;
    
    //消费者
    public class C extends Thread {
    
        private String lock;
    
        public C(String lock) {
            super();
            this.lock = lock;
        }
        
        @Override
        public void run() {
            while (true) {
                try {
                    synchronized (lock) {
                        if (ValueObject.value.equals("")) {//如果字符串为空,即被消费完了,所以wait等待。
                            lock.wait();
                        }
                        System.out.println("get的值是" + ValueObject.value);
                        ValueObject.value = "";
                        lock.notify();
                    }
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    测试:

    package com.jp.oneone;
    
    public class Run {
    
        public static void main(String[] args) {
    
            String lock = new String("");
            P p = new P(lock);
            C r = new C(lock);
    
            p.start();
            r.start();
        }
    
    }

    运行结果

    本例是1个生产者1个消费者进行数据的交互。

     多个生产者多个消费者

    实现和上面1对1基本一样,只是在测试代码中,多new几个生产者,几个消费者。

    只需注意一个问题:假死

    问题描述:所有线程都被wait,这个项目就停止运行了。

    问题原因:代码中使用wait/notify进行通信,不能保证notify唤醒的是异类(生产者唤醒消费者还是生产者),比如生产者唤醒生产者,消费者唤醒消费者,就可能导致都在等待的状态。

    问题解决:其实很简单,就是唤醒的时候同类异类都唤醒,把notify()改为natifyAll()就解决了。

    二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

      Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,在阻塞队列那一篇博文中就讲述到了,阻塞队列实际上是使用了Condition来模拟线程间协作。

    • Condition是个接口,基本的方法就是await()和signal()方法;
    • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition() 
    •  调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

      Conditon中的await()对应Object的wait();

      Condition中的signal()对应Object的notify();

      Condition中的signalAll()对应Object的notifyAll()。

     一个生产者一个消费者

     这个例子用了《Java多线程编程核心技术》中的方式,把生产者和消费者的方法写到一个类中,与生成线程和消费线程分开,感觉更高大上,当然上面的例子也可以写成这种方式。

    含有生成者消费者的类

     1 package oneone;
     2 
     3 import java.util.concurrent.locks.Condition;
     4 import java.util.concurrent.locks.ReentrantLock;
     5 
     6 public class MyService {
     7 
     8     private ReentrantLock lock = new ReentrantLock();//拿到可重入锁,相当于synchronized的作用
     9     private Condition condition = lock.newCondition();//调用await和signal方法的对象,相当于Object对象(任意对象)的的wait和notify方法
    10     private boolean hasValue = false;
    11 
    12     //生产者
    13     public void set() {
    14         try {
    15             lock.lock();//获得锁
    16             while (hasValue == true) {
    17                 condition.await(); //没被消费则阻塞该生产线程,当然也释放了锁,进入等锁的队列
    18             }
    19             System.out.println("打印★");
    20             hasValue = true;
    21             condition.signal();
    22         } catch (InterruptedException e) {
    23             e.printStackTrace();
    24         } finally {
    25             lock.unlock();
    26         }
    27     }
    28     
    29     //消费者
    30     public void get() {
    31         try {
    32             lock.lock();
    33             while (hasValue == false) {
    34                 condition.await();
    35             }
    36             System.out.println("打印☆");
    37             hasValue = false;
    38             condition.signal();
    39         } catch (InterruptedException e) {
    40             e.printStackTrace();
    41         } finally {
    42             lock.unlock();
    43         }
    44     }
    45 
    46 }

    对应生成者的线程类

    package oneone;
    
    public class MyThreadA extends Thread {
    
        private MyService myService;
    
        public MyThreadA(MyService myService) {
            super();
            this.myService = myService;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                myService.set();
            }
        }
    
    }

    对应消费者的线程类

    package oneone;
    
    
    public class MyThreadB extends Thread {
    
        private MyService myService;
    
        public MyThreadB(MyService myService) {
            super();
            this.myService = myService;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                myService.get();
            }
        }
    
    }

    测试类

    package oneone;
    
    public class Run {
    
        public static void main(String[] args) throws InterruptedException {
            MyService myService = new MyService();
    
            MyThreadA a = new MyThreadA(myService);
            a.start();
    
            MyThreadB b = new MyThreadB(myService);
            b.start();
    
        }
    }

    运行结果

     三.通过管道进行线程间通信:1)字节流;2)字符流

     Java中有各种各样的输入、输出流(Stream),其中管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据。

     一个线程发送数据到输出管道,另一个线程从输入管道读数据。

    以字节流举例:

    写数据的类,把数据写入管道输出流

    package pipeInputOutput;
    
    import java.io.IOException;
    import java.io.PipedOutputStream;
    
    public class WriteData {
    
        public void writeMethod(PipedOutputStream out) {
            try {
                System.out.println("write :");
                for (int i = 0; i < 300; i++) {
                    String outData = "" + (i + 1);
                    out.write(outData.getBytes());
                    System.out.print(outData);
                }
                System.out.println();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    读数据的类,从管道输入流读数据

    package pipeInputOutput;
    
    import java.io.IOException;
    import java.io.PipedInputStream;
    
    public class ReadData {
    
        public void readMethod(PipedInputStream input) {
            try {
                System.out.println("read  :");
                byte[] byteArray = new byte[20];
                int readLength = input.read(byteArray);
                while (readLength != -1) {
                    String newData = new String(byteArray, 0, readLength);
                    System.out.print(newData);
                    readLength = input.read(byteArray);
                }
                System.out.println();
                input.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    写数据的线程类

    package pipeInputOutput;
    
    import java.io.PipedOutputStream;
    
    public class ThreadWrite extends Thread {
    
        private WriteData write;
        private PipedOutputStream out;
    
        public ThreadWrite(WriteData write, PipedOutputStream out) {
            super();
            this.write = write;
            this.out = out;
        }
    
        @Override
        public void run() {
            write.writeMethod(out);
        }
    
    }

    读数据的线程类

    package pipeInputOutput;
    
    import java.io.PipedInputStream;
    
    public class ThreadRead extends Thread {
    
        private ReadData read;
        private PipedInputStream input;
    
        public ThreadRead(ReadData read, PipedInputStream input) {
            super();
            this.read = read;
            this.input = input;
        }
    
        @Override
        public void run() {
            read.readMethod(input);
        }
    }

    测试类:

    package pipeInputOutput;
    
    import java.io.IOException;
    import java.io.PipedInputStream;
    import java.io.PipedOutputStream;
    
    public class Run {
    
        public static void main(String[] args) {
    
            try {
                WriteData writeData = new WriteData();
                ReadData readData = new ReadData();
    
                PipedInputStream inputStream = new PipedInputStream();
                PipedOutputStream outputStream = new PipedOutputStream();
    
                //将两个Stream之间产生通信链接,这样才能将数据进行输入输出,下面两种方式都可以,其一即可
                //inputStream.connect(outputStream);
                outputStream.connect(inputStream);
    
                //开启读线程
                ThreadRead threadRead = new ThreadRead(readData, inputStream);
                threadRead.start();
    
                Thread.sleep(2000);
    
                //开启写线程
                ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
                threadWrite.start();
    
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    
    }

    运行结果

    read  :
    write :
    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300

    《Java多线程编程核心技术》

    http://www.cnblogs.com/dolphin0520/p/3920385.html

  • 相关阅读:
    vsprintf函数和va_list用法详解
    copy_from_user 详解
    OK6410之蜂鸣器buzzer字符驱
    树莓派调试PCF8591遇到的小问题
    APM关键姿态控制源码讲解
    APM姿态控制流程
    没有安装gawk
    git 第一次上传本地代码到远程仓库,解决 ! [rejected] master -> master (non-fast-forward)错误
    C++ 全面刨析使用指针方法 _new _delete
    typedef&define的用法与区别
  • 原文地址:https://www.cnblogs.com/xdyixia/p/9386133.html
Copyright © 2011-2022 走看看