1. 前言
随着摩尔定律的失效,Amdahl定律成为了多核计算机性能发展的指导。对于现在的java程序员们来说,并发编程越来越重要和习以为常。很惭愧和恐慌的是我对java的并发编程一直是只知道概念,入门都不算。最近工作需要,开始认真学习java并发编程。先找了一本简单的电子书《Java7并发编程实战手册》开始看。刚刚看到简单的生产者消费者问题,在书中给出的代码中,有一点不理解:为什么wait()语句要放在while循环之内?经过网上搜索以及翻看《effective java》第二版。终于明白了一些。特此记录下来。
2. 生产者消费者代码
生产者消费者代码如下:
数据存储类:EventStorage:(get和set标记为同步方法,并使用了wait和notify机制)
import java.util.Date;import java.util.LinkedList;import java.util.List;/** * This class implements an Event storage. Producers will storage * events in it and Consumers will process them. An event will * be a java.util.Date object * */public class EventStorage { /** * Maximum size of the storage */ private int maxSize; /** * Storage of events */ private List<Date> storage; /** * Constructor of the class. Initializes the attributes. */ public EventStorage(){ maxSize=10; storage=new LinkedList<>(); } /** * This method creates and storage an event. */ public synchronized void set(){ while (storage.size()>=maxSize){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } storage.add(new Date()); System.out.printf("Set: %d\n",storage.size()); notify(); } /** * This method delete the first event of the storage. */ public synchronized void get(){ while (storage.size()==0){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.printf("Get: %d: %s\n",storage.size(),((LinkedList<?>)storage).poll()); notify(); }}
生产者类:Producer:(调用EventStorage类中的set方法存入数据)
/** * This class implements a producer of events. * */public class Producer implements Runnable { /** * Store to work with */ private EventStorage storage; /** * Constructor of the class. Initialize the storage. * @param storage The store to work with */ public Producer(EventStorage storage){ this.storage=storage; } /** * Core method of the producer. Generates 100 events. */ @Override public void run() { for (int i=0; i<100; i++){ storage.set(); } }}
消费者类:Consumer:(调用EventStorage类中的get方法取出数据)
/** * This class implements a consumer of events. * */public class Consumer implements Runnable { /** * Store to work with */ private EventStorage storage; /** * Constructor of the class. Initialize the storage * @param storage The store to work with */ public Consumer(EventStorage storage){ this.storage=storage; } /** * Core method for the consumer. Consume 100 events */ @Override public void run() { for (int i=0; i<100; i++){ storage.get(); } }}
主类:Main:(分别启动一个生产者和一个消费者线程)
/** * Main class of the example */public class Main { /** * Main method of the example */ public static void main(String[] args) { // Creates an event storage EventStorage storage=new EventStorage(); // Creates a Producer and a Thread to run it Producer producer=new Producer(storage); Thread thread1=new Thread(producer); // Creates a Consumer and a Thread to run it Consumer consumer=new Consumer(storage); Thread thread2=new Thread(consumer); // Starts the thread thread2.start(); thread1.start(); }}
运行截图如下所示:
3. 永远不要在循环之外调用wait方法
《Effective Java》第二版中文版第69条244页位置对这一点说了一页,我看着一知半解。我能理解的一点是:对于从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()之后的程序,这样就导致向已经满了数据存储区中再次放入数据。错误产生。
下面是错误情况二的演示代码。根据第二节的代码修改而来:
数据存储类:EventStorage:(set中使用if代替while判断执行条件)
import java.util.Date;import java.util.LinkedList;import java.util.List;/** * This class implements an Event storage. Producers will storage * events in it and Consumers will process them. An event will * be a java.util.Date object * */public class EventStorage { /** * Maximum size of the storage */ private int maxSize; /** * Storage of events */ private List<Date> storage; /** * Constructor of the class. Initializes the attributes. */ public EventStorage(){ maxSize=10; storage=new LinkedList<>(); } /** * This method creates and storage an event. */ public synchronized void set(){ if (storage.size()>=maxSize){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } storage.add(new Date()); System.out.printf("Set: %d\n",storage.size()); notify(); } /** * This method delete the first event of the storage. */ public synchronized void get(){ while (storage.size()==0){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.printf("Get: %d: %s\n",storage.size(),((LinkedList<?>)storage).poll()); notify(); }}
生产者类:Producer:(调用EventStorage类中的set方法存入数据,没有修改)
消费者类:Consumer:(调用EventStorage类中的get方法取出数据,在run方法中加入了一个1ms的休眠)
import java.util.concurrent.TimeUnit;/** * This class implements a consumer of events. * */public class Consumer implements Runnable { /** * Store to work with */ private EventStorage storage; /** * Constructor of the class. Initialize the storage * @param storage The store to work with */ public Consumer(EventStorage storage){ this.storage=storage; } /** * Core method for the consumer. Consume 100 events */ @Override public void run() { for (int i=0; i<100; i++){ try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } storage.get(); } }}
主类:Main:(启动了两个生产者线程和一个消费者线程)
/** * Main class of the example */public class Main { /** * Main method of the example */ public static void main(String[] args) { // Creates an event storage EventStorage storage=new EventStorage(); // Creates a Producer and a Thread to run it Producer producer=new Producer(storage); Thread thread1=new Thread(producer); Thread thread3=new Thread(producer); // Creates a Consumer and a Thread to run it Consumer consumer=new Consumer(storage); Thread thread2=new Thread(consumer); // Starts the thread thread2.start(); thread1.start(); thread3.start(); }}
程序运行截图如下:说明存储数据区已经错误的存储了超过规定的最大存储量的数据。并发错误。
写在最后
像我一样的老程序员们,醒醒吧,学习学习java.util.concurrent包吧;学习学习java7和java8的新特性吧。再不学习,我们就要被淘汰了!
参考资料:
《Java7并发编程实战手册》
《Effective Java》第二版中文版