【java线程系列】java线程系列之线程间的交互wait()/notify()/notifyAll()及生产者与消费者模型

来源:互联网 发布:双色球算法必中6红组合 编辑:程序博客网 时间:2024/05/15 07:42

关于线程,博主写过java线程详解基本上把java线程的基础知识都讲解到位了,但是那还远远不够,多线程的存在就是为了让多个线程去协作来完成某一具体任务,比如生产者与消费者模型,因此了解线程间的协作是非常重要的,本博客主要讲解多个线程之间使用wait()/notify()/notifyAll()来进行交互的场景。

一wait()/notify()/notifyAll():

首先我们来看一下它们的函数定义:

 /* Causes the current thread to wait until another thread invokes the     * {@link java.lang.Object#notify()} method or the     * {@link java.lang.Object#notifyAll()} method for this object.     * The current thread must own this object's monitor. The thread     * releases ownership of this monitor and waits until another thread     * notifies threads waiting on this object's monitor to wake up     * This method should only be called by a thread that is the owner     * of this object's monitor.     */    public final void wait() throws InterruptedException {        wait(0);    }    public final native void wait(long timeout) throws InterruptedException;    /**     * Wakes up a single thread that is waiting on this object's     * monitor.If any threads are waiting on this object, one of them     * is chosen to be awakened. The choice is arbitrary and occurs at     * the discretion of the implementation. A thread waits on an object's     * monitor by calling one of the {@code wait} methods.     */         public final native void notify();    /* Wakes up all threads that are waiting on this object's monitor. A     * thread waits on an object's monitor by calling one of the     * {@code wait} methods.     */ public final native void notifyAll();
首先可以看到wait()/notify()/notifyAll()都是Object类中的方法,其次可以看到notify()/notifyAll(),与带一个参数的 wait(long timeout) 都被final native修饰的,即它们是本地方法且不允许被重写的。
另外从注释上我们可以获得以下信息:

1 调用某个对象的wait()方法能让当前线程阻塞,这个方法只能在拥有此对象的monitor的线程中调用(This method should only be called by a thread that is the owner of this     object's monitor.)。

2 调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程(Wakes up a single        thread that is waiting on this object's monitor.If any threads are waiting on this object, one of them is chosen to be awakened. )

3 调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程(Wakes up all threads that are waiting on this object's monitor.)


那么你可能会问为何这三个方法不是位于Thread类中而是在Object类中呢?这是因为这三个函数的操作都是与锁机制相关的,而在java中每个对象都对应一个对象锁,所以当某个线程等待某个对象的锁时,应该等待该对象来释放它自己的锁,即应该通过对象的方式来释放锁,所以将这些与锁相关的函数放在Object类中,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。


下面我们来看一下这几个函数之间的协作使用的代码示范:

public class Main {    publicstaticvoid main(String[] args) {        ThreadOne one = new ThreadOne();        one.start(); //启动一个子线程        synchronized (one) {//synchronized (one) 代表当前线程拥有one对象的锁,线程为了调用wait()或notify()方法,该线程必须是那个对象锁的拥有者            try {                System.out.println("等待对象one完成计算。。。");                one.wait(); //调用wait()方法的线程必须拥有对象one的锁,即必须在synchronized同步块中调用wait()            } catch (InterruptedException e) {                e.printStackTrace();             }             System.out.println("one对象计算的总和是:" + one.total);        }     } }  public class ThreadOne extends Thread {    int total;     publicvoid run() {        synchronized (this) {            for (int i = 0; i < 7; i++) {                total += i;             }                        notify();  //完成计算后唤醒在此对象锁上等待的单个线程,在本例中主线程Main被唤醒        }     } }
在该示例中定义了一个子线程ThreadOne,所以该程序存在两个线程,一个是默认的主线程Main,一个是自定义的ThreadOne,在自定义的线程中计算求和,在主线程中打印出计算结果,在Main线程中先启动子线程,然后调用wait()让主线程等待子线程运行,在子线程中计算完成后调用notify()唤醒Main线程打印出子线程中计算的结果。这样就做到了两个线程之间的交互。


二使用多线程模拟生产者与消费者模型:

生产者与消费者模型描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该模型的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。


解决此模型的关键是让生产者在缓冲区满时交出对临界区的占用权,自己进入等待状态,等待消费者消费产品,等消费者消费了一定量产品后再唤醒生产者生产产品。

同样,当缓冲区为空时消费者也必须等待,等待生产者生产产品,等生产者生产了一定量产品后再唤醒消费者消费产品。

代码表示如下:


public class ProducerConsumer{public static void main(String[] args){SyncStack ss = new SyncStack();Producer p = new Producer(ss);Consumer c = new Consumer(ss);Thread t1 = new Thread(p);Thread t2 = new Thread(c);t1.start();t2.start();}}class SyncStack{int cnt = 0;char[] data = new char[6];public synchronized void push(char ch){while (cnt == data.length){try{this.wait();//wait是Object 类中的方法,不是Thread中的方法,Thread中wait也是继承自Object,         //this.wait();不是让当前对象wait,而是让当前锁定this对象的线程wait,同时释放对this的锁定。  //注意:如果该对象没有被锁定,则调用wait方法就会报错!即只有在同步方法或者同步代码块中才可以调用wait方法,notify同理}catch (Exception e){}}this.notify();  //如果注释掉了本语句,可能会导致消费线程陷入阻塞(如果消费线程本身执行很慢的话,则消费线程永远不会wait,即永远不会阻塞),因为消费线程陷入阻塞, 所以生产线程因此不停生产产品达到6个后也陷入阻塞,最后显示的肯定是“容器中现在共有6个字符!”//this.notify();叫醒一个现在正在wait  this对象的一个线程,如果有多个线程正在wait this对象,通常是叫醒最先wait this对象的线程,但具体是叫醒哪一个,这是由系统调度器控制,程序员无法控制 // nority 和 notifyAll 都是Object 类中的方法data[cnt] = ch;cnt++;System.out.printf("生产了: %c\n", ch);System.out.printf("容器中现在共有%d个字符!\n\n", cnt);}public synchronized char pop(){char ch;while (0 == cnt){try{this.wait();}catch (Exception e){}}this.notify();  //如果注释掉了本语句,可能会导致生产线程陷入阻塞(如果生产线程本身执行很慢的话,则生产线程永远不会wait,即永远不会阻塞),因为生产线程陷入阻塞,消费线程因此不停取出产品,当容器中再也没有产品时消费线程也陷入阻塞,最后显示的肯定是“容器中现在共有0个字符!”ch = data[cnt-1];--cnt;System.out.printf("取出:  %c\n", ch);System.out.printf("容器中现在共有%d个字符!\n\n", cnt);return ch;}}class Producer implements Runnable{SyncStack ss = null;public Producer(SyncStack ss){this.ss = ss;}public void run(){char ch;//总共生产20个产品for (int i=0; i<20; ++i){ch = (char)('a'+i);ss.push(ch);//try//{//Thread.sleep(500);//}//catch (Exception e)//{//}}}}class Consumer implements Runnable{SyncStack ss = null;public Consumer(SyncStack ss){this.ss = ss;}//总共消费20个产品public void run(){for (int i=0; i<20; ++i){ss.pop();try{Thread.sleep(500);}catch (Exception e){}}}}


三总结:

1  如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

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

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

4一个线程被唤醒不代表它能立即获得对象的monitor,当且仅当调用完notify()或者notifyAll()且退出synchronized块,释放对象锁后,其余线程才可获得锁执行。

1 0
原创粉丝点击