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方法的更多相关文章

  1. C#中添加三个线程同时启动执行某一方法,并依次调用某方法中的循环打印输。

    添加三个线程同时启动执行某一方法,并依次调用某方法中的打印输:ABC ABC ABC ABC 实现代码如下: using System; using System.Collections.Generi ...

  2. 声明数组变量/// 计算所有元素的总和/打印所有元素总和/输出/foreach循环/数组作为函数的参数/调用printArray方法打印

    实例 下面是这两种语法的代码示例: double[] myList; // 首选的方法 或 double myList[]; // 效果相同,但不是首选方法 创建数组 Java语言使用new操作符来创 ...

  3. python基础----继承与派生、组合、接口与归一化设计、抽象类、子类中调用父类方法

    一.什么是继承                                                                          继承是一种创建新的类的方式,在pyth ...

  4. python基础之类的继承与派生、组合、接口与归一化设计、抽象类、子类中调用父类方法

    一.什么是继承 继承是一种创建新的类的方式,新建的类可以继承自一个或者多个父类,原始类称为基类或超类,新建的类称为派生类或子类. 派生:子类继承了父类的属性,然后衍生出自己新的属性,如果子类衍生出的新 ...

  5. C#编译器优化那点事 c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错 webAPI 控制器(Controller)太多怎么办? .NET MVC项目设置包含Areas中的页面为默认启动页 (五)Net Core使用静态文件 学习ASP.NET Core Razor 编程系列八——并发处理

    C#编译器优化那点事   使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的.优化代码 ...

  6. .Net中的AOP系列之《间接调用——拦截方法》

    返回<.Net中的AOP>系列学习总目录 本篇目录 方法拦截 PostSharp方法拦截 Castle DynamicProxy方法拦截 现实案例--数据事务 现实案例--线程 .Net线 ...

  7. Spring AOP不拦截从对象内部调用的方法原因

    拦截器的实现原理很简单,就是动态代理,实现AOP机制.当外部调用被拦截bean的拦截方法时,可以选择在拦截之前或者之后等条件执行拦截方法之外的逻辑,比如特殊权限验证,参数修正等操作. 但是最近在项目中 ...

  8. 前台JS(Jquery)调用后台方法 无刷新级联菜单示例

    前台用AJAX直接调用后台方法,老有人发帖提问,没事做个示例 下面是做的一个前台用JQUERY,AJAX调用后台方法做的无刷新级联菜单 http://www.dtan.so CasMenu.aspx页 ...

  9. Xilium.CefGlue利用XHR实现Js调用c#方法

    防外链 博客园原文地址在这里http://www.cnblogs.com/shen6041/p/3442499.html 引 Xilium CefGlue是个不错的cef扩展工程,托管地址在这里 ht ...

随机推荐

  1. JS中的递归

      递归基础 递归的概念 在程序中函数直接或间接调用自己 直接调用自己 简介调用自己 跳出结构,有了跳出才有结果 递归的思想 递归的调用,最终还是要转换为自己这个函数 如果有个函数foo,如果他是递归 ...

  2. (CLR-Via-C#) 类型基础

    CLR要求每个类型最终都派生自System.Object Object提供的公共方法: Equals: 如果两个对象具有相同的值,就返回true GetHashCode: 返回对象的哈希码 ToStr ...

  3. 线段树(单标记+离散化+扫描线+双标记)+zkw线段树+权值线段树+主席树及一些例题

    “队列进出图上的方向 线段树区间修改求出总量 可持久留下的迹象 我们 俯身欣赏” ----<膜你抄>     线段树很早就会写了,但一直没有总结,所以偶尔重写又会懵逼,所以还是要总结一下. ...

  4. [LeetCode] Max Chunks To Make Sorted 可排序的最大块数

    Given an array arr that is a permutation of [0, 1, ..., arr.length - 1], we split the array into som ...

  5. 【Matplotlib-01】Python 绘图库 Matplotlib 入门教程

    环境: Windows10 python3.6.4 numpy1.14.1 matplotlib2.1.2 工具:Cmder 目录: 1.线性图 2.散点图 3.饼状图 4.条形图 5.直方图 例1: ...

  6. testng中使用reportng报告

    1.pom.xml文件中添加依赖,重构一下项目(mvn compile) <dependency> <groupId>org.uncommons</groupId> ...

  7. Struts2--struts.xml详解

    通常,struts.xml文件都会继承一个struts-default.xml文件通过一些基本的拦截器来提供一些基本的配置设置之类的. 配置例: <?xml version="1.0& ...

  8. UpdateAfterEvent

    10月3日,在杭州市西湖景区,一只小松鼠不停地接受一道道食物,花生.玉米.饼干,可谓来者不拒,憨态可掬的模样吸引了众多围观者...Description   小松鼠打了10个小时的游戏,一脸满足.却发 ...

  9. [HNOI2008]越狱

    题目描述 监狱有连续编号为1...N的N个房间,每个房间关押一个犯人,有M种宗教,每个犯人可能信仰其中一种.如果相邻房间的犯人的宗教相同,就可能发生越狱,求有多少种状态可能发生越狱 输入输出格式 输入 ...

  10. ●POJ 3378 Crazy Thairs

    题链: http://poj.org/problem?id=3378 题解: 树状数组维护,高精度. 依次考虑以每个位置结尾可以造成的贡献. 假设当前位置为i,为了达到5个元素的要求,我们需要求出,在 ...