ITEEDU

入门准备

接触对象

对象导向

资源管理

对象容器(Container)

输入输出(I/O)

执行绪(Thread)

反射(Reflection)

metadata

数据库(JDBC)

舍遗补缺

Java Gossip: wait()、notify()

wait()、notify()与notifyAll()是由 Object所提供的方法,您在定义自己的类别时会继承下来(记得Java中所有的对象最顶层都继承自Object),wait()、notify()与 notifyAll()都被宣告为"final",所以您无法重新定义它们,透过这三个方法您可以控制执行绪是否为Runnable状态。

您必须在同步化的方法或区块中呼叫wait()方法,当对象的wait()方法被调用,目前的执行绪会被放入对象的「等待集合」(Wait set)中, 执行绪会释放对象的锁定,其它的执行绪可以竞争锁定,取得锁定的执行绪可以执行同步化区块;被放在等待集中的执行绪将不参与执行绪的排班,wait()可 以指定等待的时间,如果指定时间的话,则时间到之后执行绪会再度加入排班,如果指定时间0或不指定,则执行绪会持续等待,直到有被中断(interrupt)或是被告知(notify)可以参与排班。

当对象的notify()被调用,它会从对象的等待集中选出「一个」执行绪加入排班,被选出的执行绪是随机的,被选出的执行绪会与其它正在执行的执行绪共 同竞争对对象的锁定;如果您呼叫notifyAll(),则「所有」在等待集中的执行绪都会被唤醒,这些执行绪会与其它正在执行的执行绪共同竞争对对象的 锁定。

简单的说,当执行绪呼叫到对象的wait()方法时,表示它要先让出对象的被同步区使用权并等待通知,或是等待一段指定的时间,直到被通知或时间到时再从 等待点开始执行,这就好比您要叫某人作事,作到一半时某人叫您等候通知(或等候1分钟之类的),当您被通知(或时间到时)某人会继承为您服务。

说明wait()、notify()或notifyAll()的应用最常见的一个例子,就是生产者(Producer)与消费者(Consumer)的 例子,如果生产者会将产品交给店员,而消费者从店员处取走产品,店员一次只能持有固定数量产品,如果生产者生产了过多的产品,店员叫生产者等一下 (wait),如果店中有空位放产品了再通知(notify)生产者继续生产,如果店中没有产品了,店员会告诉消费者等一下(wait),如果店中有产品 了再通知(notify)消费者来取走产品。

以下举一个最简单的:生产者每次生产一个int整数交给在店员上,而消费者从店员处取走整数,店员一次只能持有一个整数。

以程序实例来看,首先是生产者:

Producer.java
package onlyfun.caterpillar;
public class Producer implements Runnable {
	private Clerk clerk;
	public Producer(Clerk clerk) {
		this.clerk = clerk;
	}
	public void run() {
		System.out.println(
		"生产者开始生产整数......");
		// 生产1到10的整数
		for(int product = 1; product <= 10; product++) {
			try {
				// 暂停随机时间
				Thread.sleep((int) (Math.random() * 3000));
			}
			catch(InterruptedException e) {
				e.printStackTrace();
			}
			// 将产品交给店员
			clerk.setProduct(product);
		}?
	}
}

再来是消费者:

Consumer.java
package onlyfun.caterpillar;
public class Consumer implements Runnable {
	private Clerk clerk;
	public Consumer(Clerk clerk) {
		this.clerk = clerk;
	}
	public void run() {
		System.out.println(
		"消费者开始消耗整数......");
		// 消耗10个整数
		for(int i = 1; i <= 10; i++) {
			try {
				// 等待随机时间
				Thread.sleep((int) (Math.random() * 3000));
			}
			catch(InterruptedException e) {
				e.printStackTrace();
			}
			// 从店员处取走整数
			clerk.getProduct();
		}
	}
}

生产者将产品放至店员,而消费者从店员处取走产品,所以店员来决定谁必须等待并等候通知。

Clerk.java
package onlyfun.caterpillar;
public class Clerk {
	// -1 表示目前没有产品
	private int product = -1;
	// 这个方法由生产者呼叫
	public synchronized void setProduct(int product) {
		if(this.product != -1) {
			try {
				// 目前店员没有空间收产品,请稍候!
				wait();
			}
			catch(InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.product = product;
		System.out.printf("生产者设定 (%d)%n", this.product);
		// 通知等待区中的一个消费者可以继续工作了
		notify();
	}
	// 这个方法由消费者呼叫
	public synchronized int getProduct() {
		if(this.product == -1) {
			try {
				// 缺货了,请稍候!
				wait();
			}
			catch(InterruptedException e) {
				e.printStackTrace();
			}
		}
		int p = this.product;
		System.out.printf(
		"消费者取走 (%d)%n", this.product);
		this.product = -1;
		// 通知等待区中的一个生产者可以继续工作了
		notify();
		return p;
	}
}

使用这么一个程序来测试:

WaitNotifyDemo.java
package onlyfun.caterpillar;
public class WaitNotifyDemo {
	public static void main(String[] args) {
		Clerk clerk = new Clerk();
		Thread producerThread = new Thread(
		new Producer(clerk));
		Thread consumerThread = new Thread(
		new Consumer(clerk));
		producerThread.start();
		consumerThread.start();
	}
}

执行结果:

生产者开始生产整数......
消费者开始消耗整数......
生产者设定 (1)
消费者取走 (1)
生产者设定 (2)
消费者取走 (2)
生产者设定 (3)
消费者取走 (3)
生产者设定 (4)
消费者取走 (4)
生产者设定 (5)
消费者取走 (5)
生产者设定 (6)
消费者取走 (6)
生产者设定 (7)
消费者取走 (7)
生产者设定 (8)
消费者取走 (8)
生产者设定 (9)
消费者取走 (9)
生产者设定 (10)
消费者取走 (10)

生产者会生产10个整数,而消费者会消耗10个整数,由于店员处只能放置一个整数,所以每生产一个就消耗一个,其结果如上所示是无误的。

如果一个执行绪进入对象的等待集中,您可以中断它的等待,这时将会发生InterruptedException例外对象,interrupt()方法可用来进行这项工作。