首页 > 编程笔记 > Java笔记 阅读:15

Java wait()、notify()和notifyAll()实现线程间通信(附带实例)

多个并发执行的线程,如果它们只是竞争资源,可以采取 synchronized 设置同步代码块来实现对共享资源的互斥访问;如果多个线程在执行的过程中有次序上的关系,那么多个线程之间必须进行通信、相互协调。

例如,经典的生产者和消费者问题。生产者和消费者共享存放产品的仓库,如果仓库为空时,消费者无法消费产品,当仓库装满时,生产者会因产品没有空间存放而无法继续生产产品。

Java 提供 3 个方法来实现线程间的通信问题,分别是 wait()、notify() 和 notifyAll()。

这三个方法只能在 synchronized 关键字起作用的范围内使用,并且是在同一个同步问题中搭配使用这三个方法时才有实际意义:
notify() 和 notifyAll() 方法都是把某个对象上等待队列内的线程唤醒,notify() 方法只能唤醒一个线程,但究竟是哪一个不能确定,而 notifyAll() 方法则是唤醒这个对象上的等待队列中的所有线程。为了安全感,大多数时候应该使用 notifiAll() 方法,除非明确知道需要唤醒是哪一个具体线程。

【实例】线程间通信示例。功能实现:下面的程序模拟了生产者和消费者的关系,生产者在一个循环中不断生产A~G的共享数据,而消费者则不断地消费生产者生产的 A~G 的共享数据。在这一对关系中,必须先有生产者生产,才能有消费者消费。

为了解决这一问题,引入了如下的等待/通知(wait()/notify())机制下:
class ShareStore {
    private char c;
    private boolean writeable = true;
    public synchronized void setShareChar(char c) {
        if (!writeable) {
            try {
                wait();        // 未消费等待
            } catch (InterruptedException e) {
            }
        }
        this.c = c;
        writeable = false;    // 标记已经生产
        notify();            // 通知消费者已经生产,可以消费
    }
    public synchronized char getShareChar() {
        if (writeable) {
            try {
                wait();        // 未生产等待
            } catch (InterruptedException e) {
            }
        }
        writeable = true;     // 标记已经消费
        notify();            // 通知需要生产
        return this.c;
    }
}

// 生产者线程
class Producer extends Thread {
    private ShareStore s;
    Producer(ShareStore s) {
        this.s = s;
    }
    public void run() {
        for (char ch = 'A'; ch <= 'G'; ch++) {
            s.setShareChar(ch);    // 生产一个新产品
            System.out.println(ch + " 被生产");
        }
    }
}

// 消费者线程
class Consumer extends Thread {
    private ShareStore s;
    Consumer(ShareStore s) {
        this.s = s;
    }
    public void run() {
        char ch;
        do {
            ch = s.getShareChar();  // 消费一个新产品
            System.out.println(ch + " 被消费");
        } while (ch != 'G');
    }
}

public class ProducerConsumer {
    public static void main(String argv[]) {
        ShareStore s = new ShareStore();  // 实例化一个 ShareStore 对象
        new Producer(s).start();         // 创建生产者线程并启动
        new Consumer(s).start();         // 创建消费者线程并启动
    }
}
上述程序执行结果为:

A 被生产
A 被消费
B 被生产
B 被消费
C 被生产
C 被消费
D 被生产
D 被消费
E 被生产
E 被消费
F 被生产
F 被消费
G 被生产
G 被消费

在实例中设置了一个通知变量,每次在生产者生产和消费者消费之前,都检测通知变量的值,根据检测结果判断是否可以生产或消费。

最开始设置通知变量的值为 true,表示还未生产。此时如果消费者需要消费,则需要修改通知变量并调用 notify() 发出通知。生产者得到通知,开始生产产品,然后修改通知变量,向消费者发出通知。

如果生产者想要继续生产,但因为检测到通知变量为 false,得知消费者还没有消费,所以调用 wait() 进入等待状态。因此,最后的结果是生产者每生产一个就通知消费者消费一个;消费者每消费一个,就通知生产者生产一个,所以不会出现未生产就消费或生产过剩的情况。

相关文章