Java提供了一套API来支持线程之间的交互。在Object类中提供了一套等待通知的API

    wait()

    notify()

    notifyAll()

    此处要注意的是,绝不要在循环外面调用wait()方法。原因如下:

    对于从wait中被notify的进程来说,它在被notify之后还需要重新检查是否符合执行条件,如果不符合,就必须再次被wait,

如果符合才能往下执行。所以:wait方法应该使用循环模式来调用。按照上面的生产者和消费者问题来说:错误情况一:如果有两个生

产者A和B,一个消费者C。当存储空间满了之后,生产者A和B都被wait,进入等待唤醒队列。当消费者C取走了一个数据后,如果调用了

notifyAll(),注意,此处是调用notifyAll(),则生产者线程A和B都将被唤醒,如果此时A和B中的wait不在while循环中而是在if

中,则A和B就不会再次判断是否符合执行条件,都将直接执行wait()之后的程序,那么如果A放入了一个数据至存储空间,则此时存储

空间已经满了;但是B还是会继续往存储空间里放数据,错误便产生了。错误情况二:如果有两个生产者A和B,一个消费者C。当存储空

间满了之后,生产者A和B都被wait,进入等待唤醒队列。当消费者C取走了一个数据后,如果调用了notify(),则A和B中的一个将被

唤醒,假设A被唤醒,则A向存储空间放入了一个数据,至此空间就满了。A执行了notify()之后,如果唤醒了B,那么B不会再次判断

是否符合执行条件,将直接执行wait()之后的程序,这样就导致向已经满了数据存储区中再次放入数据。错误产生。


    下面使用消费者与生产者问题来展示以上API的使用:

package xiancheng;

public class PC {

	public static void main(String[] args) {
		Shared s = new Shared();
		Thread t1 = new Thread(new Product(s));
		Thread t2 = new Thread(new Consumer(s));
		t1.start();
		t2.start();
	}
}
class Shared {
	
	private char c;
	private volatile boolean writeable = true;
	
	public synchronized void setChar(char ch) {
		while(!writeable) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.c = ch;
		writeable = false;
		notify();
	}
	
	public synchronized char getChar() {
		while(writeable) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		writeable = true;
		notify();
		return c;
	}
}

class Product implements Runnable{
	
	private Shared s;
	public Product(Shared s) {
		this.s = s;
	}

	@Override
	public void run() {
		for (char i = 'A'; i < 'Z'; i++) {
			s.setChar(i);
			System.out.println("生产者生产了一个" + i);
		}
	}
	
}

class Consumer implements Runnable{
	
	private Shared s;
	public Consumer(Shared s) {
		this.s = s;
	}

	@Override
	public void run() {
		char ch;
		do {
			ch = s.getChar();
			System.out.println("消费者消费了一个" + ch);
		} while (ch != 'Z');
	}
}

打印结果:

消费者消费了一个A
生产者生产了一个A
生产者生产了一个B
消费者消费了一个B
生产者生产了一个C
消费者消费了一个C
生产者生产了一个D
消费者消费了一个D
生产者生产了一个E
消费者消费了一个E
生产者生产了一个F
消费者消费了一个F
生产者生产了一个G
消费者消费了一个G
生产者生产了一个H
消费者消费了一个H
生产者生产了一个I
消费者消费了一个I
生产者生产了一个J
消费者消费了一个J
生产者生产了一个K
消费者消费了一个K
生产者生产了一个L
生产者生产了一个M
消费者消费了一个L
消费者消费了一个M
生产者生产了一个N
消费者消费了一个N
生产者生产了一个O
消费者消费了一个O
生产者生产了一个P
消费者消费了一个P
生产者生产了一个Q
消费者消费了一个Q
生产者生产了一个R
消费者消费了一个R
生产者生产了一个S
消费者消费了一个S
生产者生产了一个T
消费者消费了一个T
生产者生产了一个U
消费者消费了一个U
生产者生产了一个V
消费者消费了一个V
生产者生产了一个W
消费者消费了一个W
生产者生产了一个X
消费者消费了一个X
生产者生产了一个Y
消费者消费了一个Y

很明显第一二行就出现了问题,消费出现在了生产之前,查看代码就知道,生产与消费的顺序并没有错乱,只是打印顺序不对而已,因为唤醒动作是在打印之前,

改进代码如下:

package xiancheng;

public class PC {

	public static void main(String[] args) {
		Shared s = new Shared();
		Thread t1 = new Thread(new Product(s));
		Thread t2 = new Thread(new Consumer(s));
		t1.start();
		t2.start();
	}
}
class Shared {
	
	private char c;
	private volatile boolean writeable = true;
	
	public synchronized void setChar(char ch) {
		while(!writeable) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.c = ch;
		writeable = false;
		notify();
	}
	
	public synchronized char getChar() {
		while(writeable) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		writeable = true;
		notify();
		return c;
	}
}

class Product implements Runnable{
	
	private final Shared s;
	public Product(Shared s) {
		this.s = s;
	}

	@Override
	public void run() {
		for (char i = 'A'; i < 'Z'; i++) {
			synchronized(s) {
				s.setChar(i);
				System.out.println("生产者生产了一个" + i);
			}
		}
	}
	
}

class Consumer implements Runnable{
	
	private final Shared s;
	public Consumer(Shared s) {
		this.s = s;
	}

	@Override
	public void run() {
		char ch;
		do {
			synchronized(s) {
				ch = s.getChar();
				System.out.println("消费者消费了一个" + ch);
			}
		} while (ch != 'Z');
	}
}

运行结果:

生产者生产了一个A
消费者消费了一个A
生产者生产了一个B
消费者消费了一个B
生产者生产了一个C
消费者消费了一个C
生产者生产了一个D
消费者消费了一个D
生产者生产了一个E
消费者消费了一个E
生产者生产了一个F
消费者消费了一个F
生产者生产了一个G
消费者消费了一个G
生产者生产了一个H
消费者消费了一个H
生产者生产了一个I
消费者消费了一个I
生产者生产了一个J
消费者消费了一个J
生产者生产了一个K
消费者消费了一个K
生产者生产了一个L
消费者消费了一个L
生产者生产了一个M
消费者消费了一个M
生产者生产了一个N
消费者消费了一个N
生产者生产了一个O
消费者消费了一个O
生产者生产了一个P
消费者消费了一个P
生产者生产了一个Q
消费者消费了一个Q
生产者生产了一个R
消费者消费了一个R
生产者生产了一个S
消费者消费了一个S
生产者生产了一个T
消费者消费了一个T
生产者生产了一个U
消费者消费了一个U
生产者生产了一个V
消费者消费了一个V
生产者生产了一个W
消费者消费了一个W
生产者生产了一个X
消费者消费了一个X
生产者生产了一个Y
消费者消费了一个Y