wait() 方法的判断逻辑需要用while判断,不能用if判断,以及notify的唤醒线程的不确定性

例如,我们有一个有界队列,有生产者向队列里插入元素,有消费者从队列中获取并删除元素。

如果队列满了,则要阻塞生产者,等队列不满了再添加元素;

如果队列里没有元素,则要阻塞消费者,等到有元素了再进行消费。

因此,当生产者向队列里放进一个元素后,或者当消费者消费了队列里的一个元素后,就可以notify被阻塞的线程。

我们通过一个简单的例子来看wait()方法的判断逻辑要用while判断,而不是用if判断

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

public class EarlyNotify {

	// 有界队列
	private List<String> list = new ArrayList<String>();

	public String removeItem() throws InterruptedException {
		print("进入方法  - removeItem()");
		synchronized (list) {
			if (list.isEmpty()) { // 这里用if语句会发生危险
				print("in removeItem() - list为空  - wait()");
				list.wait();
				print("in removeItem() - 被唤醒- done with wait()");
			}
			// 删除元素
			String item = (String) list.remove(0);
			print("in removeItem() - 删除元素: '" + item + "'");
			print("离开方法  -  removeItem()");
			return item;
		}
	}

	public void addItem(String item) {
		print("进入方法  - addItem()");
		synchronized (list) {
			// 添加元素
			list.add(item);
			print("in addItem() - 添加元素: '" + item + "'");
			// 添加后,通知所有线程
			list.notifyAll();
			print("in addItem() - notifyAll线程");
		}
		print("离开方法   - addItem()");
	}

	private static void print(String msg) {
		String name = Thread.currentThread().getName();
		System.out.println(name + ": " + msg);
	}

	public static void main(String[] args) {
		final EarlyNotify en = new EarlyNotify();

		Runnable consumer = new Runnable() {
			public void run() {
				try {
					String item = en.removeItem();
				} catch (InterruptedException ix) {
					print("interrupted!");
				} catch (Exception x) {
					print("threw an Exception!!!\n" + x);
				}
			}
		};

		Runnable producer = new Runnable() {
			public void run() {
				en.addItem("Hello!");
			}
		};

		try {
			// 启动第一个删除元素的线程
			Thread threadConsumer1 = new Thread(consumer, "threadConsumer1");
			threadConsumer1.start();
			Thread.sleep(500);
			// 启动第二个删除元素的线程
			Thread threadConsumer2 = new Thread(consumer, "threadConsumer2");
			threadConsumer2.start();
			Thread.sleep(500);
			// 启动增加元素的线程
			Thread threadProducer = new Thread(producer, "threadProducer");
			threadProducer.start();

			// Thread.sleep(10000); // wait 10 seconds

			// threadA1.interrupt();
			// threadA2.interrupt();
		} catch (InterruptedException x) {
		}
	}
}

打印结果:

threadConsumer1: 进入方法  - removeItem()
threadConsumer1: in removeItem() - list为空  - wait()
threadConsumer2: 进入方法  - removeItem()
threadConsumer2: in removeItem() - list为空  - wait()
threadProducer: 进入方法  - addItem()
threadProducer: in addItem() - 添加元素: 'Hello!'
threadProducer: in addItem() - notifyAll线程
threadConsumer2: in removeItem() - 被唤醒- done with wait()
threadProducer: 离开方法   - addItem()
threadConsumer2: in removeItem() - 删除元素: 'Hello!'
threadConsumer2: 离开方法  -  removeItem()
threadConsumer1: in removeItem() - 被唤醒- done with wait()
threadConsumer1: threw an Exception!!!
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

我们也知道,notify并不能定向的唤醒某一个或某一类被阻塞的线程,它只能随机的唤醒一个线程或唤醒所有的线程,这样的弊端就造成,队列还可以再放一个元素,一个生产线程被唤醒了,放进元素后,在notify或notifyAll其他的线程,有可能唤醒的还是生产者线程,那么该生产者线程判断条件不满足后再次wait(),这样就造成了低效,在ArrayBlockingQueue中,是使用的ReentrantLock,和notFull,notEmpty两个Condition来做的,notFull用来控制生产者的put,notEmpty来控制消费者的take

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值