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

2017-03-23  本文已影响969人  liouville

[TOC]

一、wait

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

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

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

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

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的线程释放它持有的锁,而且之后还要 去和其它要持有该对象锁的线程竞争,换句话说被唤醒的线程还要满足两个条件才能获得执行权

被唤醒不一定就会执行,还要抢到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<>();
    }
}

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

上一篇下一篇

猜你喜欢

热点阅读