wait,notify方法是属于基类Object方法,调用wait方法时会将当前的线程挂起直到notify方法被调用,这和sleep方法有点类似,但是wait会释放当前线程持有调用wait方法的锁,并且wait,notify方法的调用必须必须持有调用wait,notify方法所在的对象锁,否则会抛出
java.lang.IllegalMonitorStateException
sample1 生产者和消费者模式
生产者和消费者模式是最最常见的一种模式,在这里我使用初级的wait和notify方法实现该模式,在该模式中最重要的类是MyQueue类也就是仓库,当生产者对象生产了商品放入仓库中,如果仓库有剩余容量那么直接将商品放入仓库,如果仓库已满那么要将当前的生产者线程挂起.同理消费者线程取产品的时候,如果仓库中有产品那么直接获取仓库的产品,如果仓库中没有产品那么将消费者线程挂起,下面是仓库类的实现类,该类有三个成员变量:items是仓库商品数组,capacity是仓库的总容量,而currentSize代表仓库目前商品数目
public class MyQueue<E> {
private volatile Object[] items;
private volatile int capacity;
private volatile int currentSize = 0;
public MyQueue(int capacity) {
this.capacity = capacity;
items = new Object[capacity];
}
public synchronized void add(E e) throws InterruptedException {
while (currentSize == capacity) {
wait();
}
items[currentSize] = e;
currentSize++;
notifyAll();
}
public synchronized E get() throws InterruptedException {
while (currentSize == 0) {
wait();
}
currentSize--;
E o = (E) items[currentSize];
notifyAll();
return o;
}
}
这里需要注意的是wait必须要在while循环内,目的在于即使被唤醒也要重新检查当前仓库容量,为什么呢,举个例子当目前仓库是空仓,第一个消费者获取商品被挂起,第二个消费获取商品也被挂起,当第一个生产者生产商品当前仓库容量为1,生产者唤醒了另外两个消费者,第一个消费者获取了商品仓库容量为0,紧接着第二个消费者进入同步块获取的当前currentSize就变成-1,就会抛出数组越界异常.
上面这段话总结来说一句话,线程从wait方法被唤醒后应该重新确认条件是否满足
至于消费者和生产者代码那么就很好实现了不在此贴出
sample2 打蜡与抛光
下面这个例子来自Thinking In Java,通过这个例子我们可以进一步巩固notify与wait的线程同步的使用
汽车在生产的时候必须经过先打蜡后抛光,汽车有一个成员变量maxOn是volatile,用来表示汽车是否打过蜡,volatile禁止了java的内存优化,保证变量的线程立即可见性
,下面这个例子适用于一个打蜡工人多个抛光工人的情况
public class Car {
/**
* 使用打过蜡
*/
private volatile boolean maxOn=false;
/**
* 打蜡
*/
public synchronized void wax() throws InterruptedException {
//打蜡过程
maxOn = true;
notifyAll();
}
/**
* 循环等待抛光结束
*
* @throws InterruptedException
*/
public synchronized void waitingForPolish() throws InterruptedException {
while (!maxOn) {
wait();
}
}
/**
* 抛光
*/
public synchronized void polish() {
//抛光过程
maxOn = false;
notifyAll();
}
/**
* 循环等待打蜡结束
*/
public synchronized void waitingForWax() throws InterruptedException {
while(maxOn){
wait();
}
}
}
同时在上述的代码中仍然不能忘记waitingForWax和waitingForPolish代码的循环等待,否则会出现一次打蜡多次抛光或者一个抛光多次打蜡的情况
打蜡工人核心代码如下
public void run() {
try {
while (!Thread.interrupted()) {
System.out.println("打蜡开始");
//直接进行打蜡
car.wax();
System.out.println("打蜡结束");
car.waitingForPolish();
System.out.println("第一轮打蜡抛光结束,下一轮打蜡抛光开始");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
抛光工人的核心代码如下
public void run() {
try {
while (!Thread.interrupted()) {
System.out.println("抛光开始");
//等待打蜡
car.waitingForWax();
car.polish();
System.out.println("抛光结束");
System.out.println("第一轮打蜡抛光结束,下一轮打蜡抛光开始");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
打蜡和抛光工人有一点不同,抛光工人工作的时候首先要确认打蜡是否完成,打蜡工人不需要等待直接就可以进行打蜡,这也是这个demo仅适用于一个打蜡工人多个抛光工人的原因.如果在这行代码有多个打蜡工人可能会出现一辆汽车被两次打蜡后才被抛光的情况,改良这个代码方法很简单就是将打蜡工人调用的waitingForPolish方法提前,修改打蜡工人代码如下
public void run() {
try {
while (!Thread.interrupted()) {
car.waitingForPolish();
System.out.println("打蜡开始");
car.wax();
System.out.println("打蜡结束");
System.out.println("第一轮打蜡抛光结束,下一轮打蜡抛光开始");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
总结
最原始的wait和notify同步机制是不推荐使用的,因为wait和notify对程序员要求太高,会造成很多意想不到的结果难于debug和维护,在生产环境下用的更多的是concurrent包下的并发构件