生产者消费者--wait、notify实现

[TOC]

一、wait

wait是Object的一个方法,调用wait方法立即释放当前对象锁,并且使得当前线程进入阻塞状态.

这也意味着一个对象调用wait方法时当前线程必须要持有该对象的锁,否则会抛异常.

1.线程持有对象锁的三种方式:

  • By executing a synchronized instance method of that object.
  • By executing the body of a synchronized statement that synchronizes on the object.
  • By executing the body of a synchronized statement that synchronizes on the object.
  • For objects of type Class, by executing a synchronized static method of that class.

2. 由wait而阻塞到进入可运行状态的四个条件:

  • Some other thread invokes the notify method for this object and thread T happens to be arbitrarily chosen as the thread to be awakened.
  • Some other thread invokes the notifyAll method for this object.
  • Some other thread interrupts thread T.
  • The specified amount of real time has elapsed, more or less. If timeout is zero, however, then real time is not taken into consideration and the thread simply waits until notified.

3.wait最好放在循环里面

A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice,(这难道是java语言的bug)
为了避免线程的假醒状态,wait方法最好以一下形式出现

synchronized (obj) {
     while (<condition does not hold>)
           obj.wait(timeout);
          ... // Perform action appropriate to condition
     }

原因参见:Doug Lea's "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, 2000), or Item 50 in Joshua Bloch's "Effective Java Programming Language Guide" (Addison-Wesley, 2001).

一个原因:线程在wait的同时一些变量可能已经发生改变,当该线程再次获得执行权的时候,while部分的条件可能还是不成立,如果while改成if,就可能出现问题,因此,wait最好放在while循环里面,这个问题在会发生在单生产者多消费者上

二、notify

  • notify用来唤醒一个由于wait而等待的线程,notifyAll用来唤醒全部由于wait而等待的线程。
  • The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.

解释:
被唤醒的线程要不仅要等待调用notify的线程释放它持有的锁,而且之后还要 去和其它要持有该对象锁的线程竞争,换句话说被唤醒的线程还要满足两个条件才能获得执行权

被唤醒不一定就会执行,还要抢到cpu的执行权

代码验证:

 package com.threadcommuncation;

/**
 * Created by liouville on 3/27/16.
 * 理解notify wait关键字
 */
public class NotifyWaitTest {

    private final static Object lock = new Object();

    private static boolean flag = false;
    public static void main(String[] args) {
        //lock.notify();

        for (int i = 0; i < 2; i++) {
            new ThreadWait().start();
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new ThreadA().start();
        new ThreadNotify().start();


    }


    private static  class ThreadA extends  Thread{

        @Override
        public void run() {

            while (!flag){yield();}
            synchronized (lock){
                System.out.println("Thread A抢到了锁");
            }
        }
    }

    private static  class ThreadWait extends  Thread{

        @Override
        public void run() {


            synchronized (lock){

                System.out.println("线程" + getName() + "即将等待");
                try {
                    /**
                     * 调用wait方法立即释放当前对象锁,并且使得当前线程进入阻塞状态.
                     * 这也意味着一个对象调用wait方法时当前线程必须要持有该对象的锁,否则会抛异常.
                      */

                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("线程" + getName() + "结束");
        }
    }



    /**
     * The awakened thread will not be able to proceed until the current
     * thread relinquishes the lock on this object. The awakened thread
     * will compete in the usual manner with any other threads that might
     * be actively competing to synchronize on this object; for example,
     * the awakened thread enjoys no reliable privilege or disadvantage
     * in being the next thread to lock this object.  --doc
     *
     * 说明两点:
     *  被唤醒的线程要不仅要等待调用notify的线程释放它持有的锁,而且之后还要        去和其它要持有该对象锁的
     *  线程去竞争
     */
    private static  class ThreadNotify extends  Thread{

        @Override
        public void run() {

            synchronized (lock){
                System.out.println("notify 线程抢到锁");
                lock.notifyAll();
                flag = true;

            }
        }
    }


}

多运行几次,发现输出:
线程Thread-0即将等待
线程Thread-1即将等待
notify 线程抢到锁
Thread A抢到了锁
线程Thread-1结束
线程Thread-0结束

三、生产者和消费者问题

1.问题简述

生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。
该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数 -- 维基百科

我的说法:生产者、消费者、仓库
生产者负责往仓库里面加物品,消费者负责取,仓库为空时不能取物品,满时不呢加物品.如何协调生产者和消费者满足这个条件?可以用线程间通信原语wait notify解决

2.单生产单消费


package com.threadcommuncation.PAndC;

import java.util.ArrayList;

/**
 * Created by liouville on 3/27/16.
 * 单生产者单消费者,
 *
 *  用wait notify实现线程间通信
 *
 * Res.list.size == 1时,只能消费;等于0时,只能生产
 */
public class SimplePAndC {

    private static final Object lock = new Object();
    public static void main(String[] args) {
        new ProductThread().start();
        new ConsumerThread().start();
    }

    private static class ProductThread extends Thread{
        @Override
        public void run() {
            super.run();
            while (true){
                synchronized (lock){
                    if (Res.list.size() != 0){

                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    Res.list.add("h");
                    System.out.println(getName() + ": 生产者生产一个元素");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.notify();
                }

            }


        }
    }

    private static class ConsumerThread extends Thread{
        @Override
        public void run() {
            super.run();
            while(true){
                synchronized (lock){
                    while(Res.list.size() == 0){

                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Res.list.remove(0);
                    System.out.println(getName() + ": 消费者消费一个元素");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.notify();
                }

            }

        }
    }


    private static class Res{
        static ArrayList<String> list = new ArrayList<>();
    }
}

3.在单生产单消费的基础之上实现单生产多消费

package com.threadcommuncation.PAndC;

import java.util.ArrayList;

/**
 * Created by liouville on 3/27/16.
 * 单生产者多消费者,
 *
 * Res.list.size == 1时,只能消费;等于0时,只能生产
 *
 * 两个问题:
 *
 * 1.数组越界:因为有这么一种情况,
 *
 *      由于最开始list.size == 0,如果消费者A得到了执行,进入等待状态.
 *      生产者生产完一个元素以后唤醒消费者A,但是唤醒不一定代表能抢到对象锁,
 *     若消费者B抢到了执行权,此时消费B者顺利消费一个元素,之后唤醒的是消费者A.
 *     由于此时list.size == 0,消费一个元素就会报空指针异常.
 *
 *      解决办法:改if为while
 *
 * 2.所有线程都在等待的问题:因为改if为while,所以可能出现消费者唤醒消费者的情况,使得被唤醒的生产者
 * 继续等待,而生产者也处于等待状态,所以所有线程都处于等待状态,
 *      解决办法:改notify为notifyAll即可
 */
public class PAndCS {

    private static final Object lock = new Object();
    public static void main(String[] args) {
        new ProductThread().start();

        for (int i = 0; i < 3; i++) {
            new ConsumerThread().start();
        }
    }

    private static class ProductThread extends Thread{
        @Override
        public void run() {
            super.run();
            while (true){
                synchronized (lock){
                    if (Res.list.size() != 0){

                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    Res.list.add("h");
                    System.out.println(getName() + ": 生产者生产一个元素");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.notify();
                }

            }


        }
    }

    private static class ConsumerThread extends Thread{
        @Override
        public void run() {
            super.run();
            while(true){
                synchronized (lock){
                    while(Res.list.size() == 0){

                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Res.list.remove(0);
                    System.out.println(getName() + ": 消费者消费一个元素");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.notifyAll();
                }

            }

        }
    }


    private static class Res{
        static ArrayList<String> list = new ArrayList<>();
    }
}

多生产和多消费与之类似.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容