永远不要在循环之外调用wait方法
2016-03-03 12:53
489 查看
1. 前言
随着摩尔定律的失效,Amdahl定律成为了多核计算机性能发展的指导。对于现在的java程序员们来说,并发编程越来越重要和习以为常。很惭愧和恐慌的是我对java的并发编程一直是只知道概念,入门都不算。最近工作需要,开始认真学习java并发编程。先找了一本简单的电子书《Java7并发编程实战手册》开始看。刚刚看到简单的生产者消费者问题,在书中给出的代码中,有一点不理解:为什么wait()语句要放在while循环之内?经过网上搜索以及翻看《effective java》第二版。终于明白了一些。特此记录下来。2. 生产者消费者代码
生产者消费者代码如下:数据存储类:EventStorage:(get和set标记为同步方法,并使用了wait和notify机制)
[code] 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方法存入数据)
[code]/** * 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方法取出数据)
[code]/** * 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:(分别启动一个生产者和一个消费者线程)
[code]/** * 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判断执行条件)
[code]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的休眠)
[code]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:(启动了两个生产者线程和一个消费者线程)
[code]/** * 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》第二版中文版
相关文章推荐
- DLL中dllmain重定义的解决办法
- Fatal: the Postfix mail system is already running 的解决方案
- List对于自定义类型,使用contains
- Email 下载音频附件完成后播放不显示名称
- wait_event_interruptible 使用方法
- 如何: 如何提供自己 DllMain MFC 的规则 DLL 中
- 【转】traits技术及模板偏特化
- 运行目录和工作目录 http://blog.csdn.net/ghevinn/article/details/17399001
- 游戏 人工智能
- [LeetCode]217. Contains Duplicate
- 出现( linker command failed with exit code 1)错误总结
- 怎么解决Failed to load the JNIshared library
- MFC 改变窗体颜色 颜色渐变 内存DC CPaintDC
- 看看baidu是如何AJAX跨域的[转]
- WinMain初始化详细过程以及消息循环
- 如何使用Gmail的别名功能?
- Linux下使用netfilter进行IP包解析 http://blog.csdn.net/kingskyleader/article/details/7701140
- main函数的参数
- 两个netfilter的例子 http://blog.csdn.net/CaspianSea/article/details/43730021
- 2.6.30内核Netfilter的简单例子、一(DropAll) http://blog.csdn.net/sahusoft/article/details/4540886