Java Concurrency - wait & notify, 等待通知机制

生产者消费者问题是一个常见的多线程同步案例:一组生产者线程和一组消费者线程共享一个初始状态为空、大小为 N 的缓冲区。只有当缓冲区没满的时候,生产者才能把消息放入缓冲区,否则必须等待;只有缓冲区不空的时候,消费者才能从缓冲区取消息,否则必须等待。由于缓冲区是临界资源,在同一时间,它只允许一个生产者放入消息,或者一个消费者从中取出消息。

生产者消费者问题涉及到了多个线程通信的等待通知机制,生产者线程和消费者只能满足一定条件下才能操作共享的缓冲区,否则只能挂起等待,直到被唤醒重新竞争共享资源。Java API 提供了几个方法来实现这一机制:wait、notify、notifyAll。

wait: 在调用 wait 方法之前,线程必须获得该对象的对象级别锁。即只能在同步方法或同步块中调用 wait 方法,否则 JVM 会抛出 IllegalMonitorStateException 异常。在执行 wait 方法后,当前线程会释放锁。在 wait 方法返回之前,线程和其他线程重新竞争该对象锁。

notify: 在调用 notify 方法之前,线程同样须要获得该对象的对象级别锁,否则 JVM 会抛出 IllegalMonitorStateException 异常。notify 方法通知那些呈 wait 态的其他线程,如果有多个线程等待,则选择其中一个线程准备获得该对象锁。notify 方法执行完毕,呈 wait 状态的线程并不会立即获得该对象锁,须等到执行 notify 方法的线程执行完同步方法或同步块后,对象锁才会被释放,呈 wait 状态的线程才能获得锁。

notifyAll: notifyAll 与 notify 在于,notify 只唤醒一个等待锁的线程,而 notifyAll 会唤醒所有等待锁的线程。

 

生产者消费者问题示例

临界资源缓冲区

import java.util.ArrayList;
import java.util.List;

/**
 * 仓库
 * @author  huey
 * @created 2016年10月19日
 * @updated 2016年10月19日
 * @version 1.0.0
 */
public class Storehouse {

    private List<Object> buffer;
    private int maxSize;            // 仓库最多允许存储 maxSize 个物品
    
    public Storehouse(int maxSize) {
        if (maxSize <= 0 || maxSize > 100) {
            throw new IllegalArgumentException("maxSize 参数的值必须在 1 到 100之间");
        }
        this.maxSize = maxSize;
        buffer = new ArrayList<Object>();
    }
    
    public synchronized void add(Object data) {
        if (buffer.size() == maxSize) {
            throw new IndexOutOfBoundsException("仓库已经饱和,不能再输入物品。");
        }
        buffer.add(data);
        System.out.printf("输入物品,仓库当前有 %d 个物品。\n", buffer.size());
    }
    
    public synchronized void remove() {
        buffer.remove(0);
        System.out.printf("输出物品,仓库当前有 %d 个物品。\n", buffer.size());
    }
    
    public boolean isEmpty() {
        return buffer.isEmpty();
    }
    
    public boolean isFull() {
        return buffer.size() == maxSize;
    }
}

生产者

import java.util.Random;

/**
 * 生产者
 * @author  huey
 * @created 2016年10月19日
 * @updated 2016年10月19日
 * @version 1.0.0
 */
public class Producer extends Thread {
    
    private Storehouse storehouse;
    
    public Producer(String name, Storehouse storehouse) {
        super(name);
        this.storehouse = storehouse;
    }

    /**
     * 生产者生产物品
     * @author  huey
     * @created 2016年10月19日
     * @updated 2016年10月19日
     */
    public void produce() {
        synchronized (storehouse) {
            while (storehouse.isFull()) {
                try {
                    System.out.printf("仓库当前处于饱和状态,生产者  %s 须挂起等待。\n", Thread.currentThread().getName());
                    storehouse.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            storehouse.add(new Object());
            storehouse.notifyAll();
        }
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            this.produce();
            try {
                Thread.sleep(new Random().nextInt(50));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者

import java.util.Random;

/**
 * 消费者
 * @author  huey
 * @created 2016年10月19日
 * @updated 2016年10月19日
 * @version 1.0.0
 */
public class Consumer extends Thread {

    private Storehouse storehouse;
    
    public Consumer(String name, Storehouse storehouse) {
        super(name);
        this.storehouse = storehouse;
    }
    
    /**
     * 消费者消费物品
     * @author  huey
     * @created 2016年10月18日
     * @updated 2016年10月18日
     */
    public void consume() {
        synchronized (storehouse) {
            while (storehouse.isEmpty()) {
                try {
                    System.out.printf("仓库当前处于空虚状态,消费者 %s 须挂起等待。\n", Thread.currentThread().getName());
                    storehouse.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            storehouse.remove();
            storehouse.notifyAll();
        }
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            this.consume();
            try {
                Thread.sleep(new Random().nextInt(100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

测试用例

/**
 * 
 * @author  huey
 * @created 2016年10月19日
 * @updated 2016年10月19日
 * @version 1.0.0
 */
public class Main {
    public static void main(String[] args) {
        Storehouse storehouse = new Storehouse(5);
        
        Thread[] producers = new Thread[] {
            new Producer("P1", storehouse),
            new Producer("P2", storehouse)
        };
        for (Thread producer : producers) {
            producer.start();
        }
        
        Thread[] consumers = new Thread[] {
            new Consumer("C1", storehouse),
            new Consumer("C2", storehouse),
            new Consumer("C3", storehouse),
            new Consumer("C4", storehouse)
        };
        for (Thread consumer : consumers) {
            consumer.start();
        }
    }    
}

运行结果:

输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
仓库当前处于空虚状态,消费者 C2 须挂起等待。
仓库当前处于空虚状态,消费者 C1 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C2 须挂起等待。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
仓库当前处于空虚状态,消费者 C1 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
仓库当前处于空虚状态,消费者 C2 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
仓库当前处于空虚状态,消费者 C2 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
输入物品,仓库当前有 1 个物品。
输入物品,仓库当前有 2 个物品。
输出物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C2 须挂起等待。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C2 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
仓库当前处于空虚状态,消费者 C1 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C1 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。

 

posted on 2016-10-19 10:43  huey2672  阅读(365)  评论(0编辑  收藏  举报