永远不要在循环之外调用wait方法
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》第二版中文版
永远不要在循环之外调用wait方法的更多相关文章
- C#中添加三个线程同时启动执行某一方法,并依次调用某方法中的循环打印输。
添加三个线程同时启动执行某一方法,并依次调用某方法中的打印输:ABC ABC ABC ABC 实现代码如下: using System; using System.Collections.Generi ...
- 声明数组变量/// 计算所有元素的总和/打印所有元素总和/输出/foreach循环/数组作为函数的参数/调用printArray方法打印
实例 下面是这两种语法的代码示例: double[] myList; // 首选的方法 或 double myList[]; // 效果相同,但不是首选方法 创建数组 Java语言使用new操作符来创 ...
- python基础----继承与派生、组合、接口与归一化设计、抽象类、子类中调用父类方法
一.什么是继承 继承是一种创建新的类的方式,在pyth ...
- python基础之类的继承与派生、组合、接口与归一化设计、抽象类、子类中调用父类方法
一.什么是继承 继承是一种创建新的类的方式,新建的类可以继承自一个或者多个父类,原始类称为基类或超类,新建的类称为派生类或子类. 派生:子类继承了父类的属性,然后衍生出自己新的属性,如果子类衍生出的新 ...
- C#编译器优化那点事 c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错 webAPI 控制器(Controller)太多怎么办? .NET MVC项目设置包含Areas中的页面为默认启动页 (五)Net Core使用静态文件 学习ASP.NET Core Razor 编程系列八——并发处理
C#编译器优化那点事 使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的.优化代码 ...
- .Net中的AOP系列之《间接调用——拦截方法》
返回<.Net中的AOP>系列学习总目录 本篇目录 方法拦截 PostSharp方法拦截 Castle DynamicProxy方法拦截 现实案例--数据事务 现实案例--线程 .Net线 ...
- Spring AOP不拦截从对象内部调用的方法原因
拦截器的实现原理很简单,就是动态代理,实现AOP机制.当外部调用被拦截bean的拦截方法时,可以选择在拦截之前或者之后等条件执行拦截方法之外的逻辑,比如特殊权限验证,参数修正等操作. 但是最近在项目中 ...
- 前台JS(Jquery)调用后台方法 无刷新级联菜单示例
前台用AJAX直接调用后台方法,老有人发帖提问,没事做个示例 下面是做的一个前台用JQUERY,AJAX调用后台方法做的无刷新级联菜单 http://www.dtan.so CasMenu.aspx页 ...
- Xilium.CefGlue利用XHR实现Js调用c#方法
防外链 博客园原文地址在这里http://www.cnblogs.com/shen6041/p/3442499.html 引 Xilium CefGlue是个不错的cef扩展工程,托管地址在这里 ht ...
随机推荐
- create-keyframe-animation-js实现css3动画
使用JavaScript在浏览器中动态生成CSS关键帧动画. enter(el, done) { // 设置动画帧数 let animation = { 0: { transform: `transl ...
- MySQL操作与修改表
插入数据(insert) insert语句的3个主要组成部分: 所要插入数据的表的名称: 表终需要使用的列的名称: 需要插入到列的值. 数字型主键生成机制 数字型主键生成机制,除了随机选择数字外,还可 ...
- NSURLSession使用, 后台下载
现在越来越多的开发习惯于使用各种第三方框架,诚然,第三方框架给我们开发带来了很多便利,但我们不能太依赖于第三方,在使用第三方的同时学习其原理才是硬道理. 所以今天我们就来讲讲AFNetworking所 ...
- JS小测验
1.编写一个方法method(),判断一个数能否同时被3和5整除 <div class="one" onClick="method()"> func ...
- C#之winform实现文件拖拽功能
将一个文件拖拽到窗体的某个控件时,将该控件的路径显示在该控件上,只要拿到了路径自然可以读取文件中的内容了 将一个控件的属性AllowDrop设置为true,然后添加DragDrop.DragEnter ...
- js变量的生命周期
1.在JavaScript中,对于for循环中定义的i变量,其生命周期在循环结束后仍然是有效的. for (var i=0; i < 10; i++){ doSomething(i); } al ...
- ng-select 下拉的两种方式
<!doctype html><html lang="en"><head> <meta charset="UTF-8" ...
- [Awson原创]网络(network)
Description Awson是某国际学校信竞组的一只菜鸡.学校为了使教育信息化,打算在学校内新建机房,并且为机房联网.但吝啬的学校又不想花费过多的开销,于是将规划 网络路线的任务交给了信竞组的A ...
- NOIP 2011 观光公交
题目描述 风景迷人的小城Y 市,拥有n 个美丽的景点.由于慕名而来的游客越来越多,Y 市特意安排了一辆观光公交车,为游客提供更便捷的交通服务.观光公交车在第 0 分钟出现在 1号景点,随后依次前往 2 ...
- [BZOJ]1023 cactus仙人掌图(SHOI2008)
NOIP后的第一次更新嗯. Description 如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人掌图(cactus).所谓简单回路就是指在 ...