Java中的等待/通知机制

1.什么是等待/通知机制?

多个线程之间也可以实现通信,原因就是多个线程共同访问同一个变量。但是这种通信机制不是 “等待/通知” ,两个线程完全是主动地读取一个共享变量。
简单的说,等待/通知机制就是一个【线程A】等待,一个【线程B】通知(线程A可以不用再等待了)。

2.Java中的实现

在Java语言中,实现等待通知机制主要是用:wait()/notify()方法实现。下面详细介绍一下这两个方法:

  • wait()方法 :wait()方法是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“欲执行队列”中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止。在调用wait()方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。
  • notify()方法:方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。【但是如果使用了notify(),那么执行这个notify()的代码的线程会是什么状态?】

这两个方法都需要同synchronized关键字联用。

3.具体案例

3.1 语法验证

package grammar.Thread.Chapter3;

public class TestWait {   
 public static void main(String[] args) {
        String newString = new String("");
        try {
            newString.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行抛出如下异常:
1.java.lang.IllegalMonitorStateException,具体的原因是:没有“对象监视器”,也就是没有同步加锁。修改如下:

public static void main(String[] args) {
        String lock = new String();
        System.out.println("above synchronized");
        synchronized (lock) {
            System.out.println("the first line");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("after wait");
        }
        System.out.println("after synchronized");
    }

此示例运行成功,但是会运行到lock.wait()时卡住,因为没有notify()唤醒它。

3.2 单消费者/单生产者

  • producer类
package InAction.ProducerAndConsumer;

import java.util.Stack;

public class Producer{

    Stack<Double> stack;
    //构造注入
    public Producer(Stack<Double> stack ){
        this.stack = stack;
    }

    public void produce(){
        while(true) {
            if (stack.size() == 0) {
                synchronized (stack) {
                    double product = Math.random() * 10;
                    stack.push(product);
                    System.out.println("produce one product,the stack.size ="
                     + stack.size());
                    try {
                        stack.wait();//阻塞
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
  • Consumer类
package InAction.ProducerAndConsumer;

import java.util.Stack;

public class Consumer{
    Stack<Double> stack;
    public Consumer(Stack stack){
        this.stack = stack;
    }

    //消费消息
    public void consume(){
        while(true) {
            synchronized (stack) {
                if (stack.size() != 0) {
                    double temp = stack.pop();//出栈
                    System.out.println("consume one product" + temp
                     + ",stack.size = " + stack.size());
                    stack.notify();//wake up producer
                }
            }
        }
    }
}
  • ThreadOne类【具体线程类】
package InAction.ProducerAndConsumer;

public class ThreadOne extends Thread{
    Producer producer;

    //构造注入producer
    public ThreadOne(Producer producer){
        super();
        this.producer = producer;
    }

    @Override
    public void run() {
        producer.produce();
    }
}
  • ThreadTwo类
package InAction.ProducerAndConsumer;

public class ThreadTwo extends Thread {
    Consumer consumer;

    //构造注入producer
    public ThreadTwo(Consumer consumer){
        super();
        this.consumer = consumer;
    }

    @Override
    public void run() {
        consumer.consume();
    }
}
  • Client类【用于main函数测试】
package InAction.ProducerAndConsumer;

import java.util.Stack;

public class Client {

    public static void main(String[] args) {
        //生成或消费的记录都放在stack中
        Stack<Double> stack = new Stack<Double>();
        Producer producer = new Producer(stack);
        Consumer consumer = new Consumer(stack);

        ThreadOne threadOne = new ThreadOne(producer);
        ThreadTwo threadTwo = new ThreadTwo(consumer);

        threadOne.start();
        threadTwo.start();
    }
}

运行结果:
Java中的等待/通知机制_多线程
可以看到先生产,再消费。而且这个过程是一直持续下去的。
注:

  • 1.ThreadOne和ThreadTwo继承Thread类,并非是Consumer/Producer继承Thread类去实现。而是由不同的线程去执行这个代码。

3.3 多个生产者,一个消费者消费

ThreadA与TheradB类不变。
为了体现是哪个生产者在生产产品,对Producer类和Consumer类进行修改,如下:

  • Producer
package InAction.ProducerAndConsumer;

import java.util.Stack;

public class Producer{

    Stack<Integer> stack;
    //构造注入
    public Producer(Stack<Integer> stack ){
        this.stack = stack;
    }

    public void produce(){
        while(true) {
            if (stack.size() == 0){
                synchronized (stack) {
                    int product = (int)(Math.random() * 10);
                    stack.push(product);
                    System.out.println(Thread.currentThread().getName()+
                            " produce one product "+product+
                            ",the stack.size ="+ stack.size());
                    try {
                        stack.wait();//阻塞
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
  • Consumer
package InAction.ProducerAndConsumer;

import java.util.Stack;

public class Consumer{
    Stack<Integer> stack;
    public Consumer(Stack stack){
        this.stack = stack;
    }

    //消费消息
    public void consume(){
        while(true) {
            synchronized (stack) {
                if (stack.size() != 0) {
                    int temp = stack.pop();//出栈
                    System.out.println(Thread.currentThread().getName()+
                            " consume one product " + temp +
                            ",stack.size = " + stack.size());
                    stack.notify();//wake up producer
                }
            }
        }
    }
}

修改Client类,使其有多个生产者生产产品:

  • Client
package InAction.ProducerAndConsumer;

import java.util.Stack;

public class Client {

    public static void main(String[] args) {
        //生成或消费的记录都放在stack中
        Stack<Integer> stack = new Stack<Integer>();
        Producer producer = new Producer(stack);
        Consumer consumer = new Consumer(stack);

        //弄两个生产者
        ThreadOne pA = new ThreadOne(producer);
        ThreadOne pA1 = new ThreadOne(producer);

        //仍然只有一个消费者
        ThreadTwo threadTwo = new ThreadTwo(consumer);

        pA.setName("pA");
        pA1.setName("pB");
        threadTwo.setName("cA");

        pA.start();
        pA1.start();
        threadTwo.start();
    }
}

运行结果:
Java中的等待/通知机制_多线程_02
可以看到,有时产品栈stack中有1个产品【消费者会消费1次】,有时有2个产品【接下来消费者会消费两次】。