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