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实现两种实用的排序算法——冒泡、快速排序

      分类:js (4443) (0) 零:数据准备,给定数组arr=[2,5,4,1,7,3,8,6,9,0]; 一:冒牌排序 1思想:冒泡排序思想:每一次对比相邻两个数据的大小,小的排在前面,如果前 ...

  2. Html5 监听拦截Android返回键方法详解

    浏览器窗口有一个history对象,用来保存浏览历史. 如果当前窗口先后访问了三个网址,那么history对象就包括三项,history.length属性等于3. history对象提供了一系列方法, ...

  3. MongoDB 分片集群搭建

    一.概述 分片是一种在多台机器上分配数据的方法.MongoDB使用分片来支持具有非常大的数据集和高吞吐量操作.有两种解决系统增长的方法:垂直扩展和水平扩展. 垂直扩展涉及增加单个服务器的容量,例如使用 ...

  4. Java知IO

    ---恢复内容开始--- Java将IO(文件.网络.终端)封装成非常多的类,看似繁杂,其实每个类的具有独特的功能. 按照存取的对象是二进制还是文本,java使用字节流和字符流实现IO. 流是java ...

  5. python 作业 编写登陆接口

    # -*- coding:utf-8 -*-import os,sysfor i in range(3):#进行3次循环 blacklist = open(r"C:\Users\Pc4\De ...

  6. bzoj 5248: [2018多省省队联测]一双木棋

    Description 菲菲和牛牛在一块n行m列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手.棋局开始时,棋盘上没有任何棋子, 两人轮流在格子上落子,直到填满棋盘时结束.落子的规则是:一个格子可以落子 ...

  7. SPOJ PHRASES 每个字符串至少出现两次且不重叠的最长子串

    Description You are the King of Byteland. Your agents have just intercepted a batch of encrypted ene ...

  8. [BZOJ]1093 最大半连通子图(ZJOI2007)

    挺有意思的一道图论. Description 一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:∀u,v∈V,满足u→v或v→u,即对于图中任意两点u,v,存在一条u到v ...

  9. K-means聚类 的 Python 实现

    K-means聚类 的 Python 实现 K-means聚类是一个聚类算法用来将 n 个点分成 k 个集群. 算法有3步: 1.初始化– K 个初始质心会被随机生成 2.分配 – K 集群通过关联到 ...

  10. google-gson 解析json

    http://www.cnblogs.com/jianyungsun/p/6647203.html 在JSON官网我们可以查看到各个语法对json的支持,对于java来说比较成熟的是google-gs ...