zoukankan      html  css  js  c++  java
  • Java多线程协作(wait、notify、 notifyAll)

    http://sunjun041640.blog.163.com/blog/static/25626832201041411210560/

    Java监视器支持两种线程:互斥和 协作。

       前面我们介绍了采用对象锁和重入锁来实现的互斥。这一篇中,我们来看一看线程的协作。

       举个例子:有一家汉堡店举办吃汉堡比赛,决赛时有3个顾客来吃,3个厨师来做,一个服务员负责协调汉堡的数量。为了避免浪费,制作好的汉堡被放进一 个能装有10个汉堡的长条状容器中,按照先进先出的原则取汉堡。如果容器被装满,则厨师停止做汉堡,如果顾客发现容器内的汉堡吃完了,就可以拍响容器上的 闹铃,提醒厨师再做几个汉堡出来。此时服务员过来安抚顾客,让他等待。而一旦厨师的汉堡做出来,就会让服务员通知顾客,汉堡做好了,让顾客继续过来取汉 堡。

        这里,顾客其实就是我们所说的消费者,而厨师就是生产者。容器是决定厨师行为的监视器,而服务员则负责监视顾客的行为。

    在JVM中,此种监视器被称为等待并唤醒监视器。



        在这种监视器中,一个已经持有该监视器的线程,可以通过调用监视对象的wait方法,暂停自身的执行,并释放监视器,自己进入一个等待区,直到监 视器内的其他线程调用了监视对象的notify方法。 当一个线程调用唤醒命令以后,它会持续持有监视器,直到它主动释放监视器。而这之后,等待线程会苏醒,其中的一个会重新获得监视器,判断条件状态,以便决 定是否继续进入等待状态或者执行监视区域,或者退出。

    请看下面的代码:
    package hsj.test;
    
    public class NotifyTest {
        private String flag = "true";
    
        class NotifyThread extends Thread {
            public NotifyThread(String name) {
                super(name);
            }
    
            public void run() {
                try {
                    sleep(3000);// 推迟3秒钟通知
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                flag = "false";
                flag.notify();
            }
        };
    
        class WaitThread extends Thread {
            public WaitThread(String name) {
                super(name);
            }
    
            public void run() {
    
                while (flag != "false") {
                    System.out.println(getName() + " begin waiting!");
                    long waitTime = System.currentTimeMillis();
                    try {
                        flag.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    waitTime = System.currentTimeMillis() - waitTime;
                    System.out.println("wait time :" + waitTime);
                }
                System.out.println(getName() + " end waiting!");
    
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println("Main Thread Run!");
            NotifyTest test = new NotifyTest();
            NotifyThread notifyThread = test.new NotifyThread("notify01");
            WaitThread waitThread01 = test.new WaitThread("waiter01");
            WaitThread waitThread02 = test.new WaitThread("waiter02");
            WaitThread waitThread03 = test.new WaitThread("waiter03");
            notifyThread.start();
            waitThread01.start();
            waitThread02.start();
            waitThread03.start();
        }
    
    }

    这段代码启动了三个简单的wait线程,当他们处于等待状态以后,试图由一个notify线程来唤醒。

    运行这段程序,你会发现,满屏的java.lang.IllegalMonitorStateException,根本不是你想要的结果。

    请注意以下几个事实:
       1. 任何一个时刻,对象的控制权(monitor)只能被一个线程拥有。
       2. 无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权(monitor)。
       3. 如果在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException异常。
       4. JVM基于多线程,默认情况下不能保证运行时线程的时序性。

    也就是说,当线程在调用某个对象的wait或者notify方法的时候,要先取得该对象的控制权,换句话说,就是进入这个对象的监视器。

    通过前面对同步的讨论,我们知道,要让一个线程进入某个对象的监视器,通常有三种方法:

    1: 执行对象的某个同步实例方法
    2: 执行对象对应的同步静态方法
    3: 执行对该对象加同步锁的同步块

    显然,在上面的例程中,我们用第三种方法比较合适。

    于是我们将上面的wait和notify方法调用包在同步块中。
                  synchronized (flag) {
                    flag = "false";
                    flag.notify();
                }
    
                synchronized (flag) {
                    while (flag != "false") {
                        System.out.println(getName() + " begin waiting!");
                        long waitTime = System.currentTimeMillis();
                        try {
                            flag.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        waitTime = System.currentTimeMillis() - waitTime;
                        System.out.println("wait time :" + waitTime);
                    }
                    System.out.println(getName() + " end waiting!");
                }



    但是,运行这个程序,我们发现事与愿违。那个非法监视器异常又出现了。。。
    我们注意到,针对flag的同步块中,我们实际上已经更改了flag对对象的引用: flag="false";
    显然,这样一来,同步块也无能为力了,因为我们根本不是针对唯一的一个对象在进行同步。

    我们不妨将flag封装到JavaBean或者数组中去,这样用JavaBean对象或者数组对象进行同步,就可以达到既能修改里面参数又不耽误同步的目 的。

        private   String flag[] = {"true"};
    
                synchronized (flag) {
                    flag[0] = "false";
                    flag.notify();
                }
    
                synchronized (flag) {
                    while (flag[0] != "false") {
                        System.out.println(getName() + " begin waiting!");
                        long waitTime = System.currentTimeMillis();
                        try {
                            flag.wait();
    
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        waitTime = System.currentTimeMillis() - waitTime;
                        System.out.println("wait time :" + waitTime);
                    }
                    System.out.println(getName() + " end waiting!");
                }


    运行这个程序,看不到异常了。但是仔细观察结果,貌似只有一个线程被唤醒。利用jconsole等工具查看线程状态,发现的确还是有两个线程被阻塞的。这 是为啥呢?

    程序中使用了flag.notify()方法。只能是随机的唤醒一个线程。我们可以改用flag.notifyAll()方法。这样,所有被阻塞的线程都 会被唤醒了。

    最终代码请读者自己修改,这里不再赘述。

    好了,亲爱的读者们,让我们回到开篇提到的汉堡店大赛问题当中去,来看一看厨师、服务生和顾客是怎么协作进行这个比赛的。

    首先我们构造故事中的三个次要对象:汉堡包、存放汉堡包的容器、服务生
    // 服务生,这是个配角,不需要属性。
        public class Waiter {
        }
    
        // 汉堡包
        class Hamberg {
            
            private int id;// 汉堡编号
            private String cookerid;// 厨师编号
    
            public Hamberg(int id, String cookerid) {
                this.id = id;
                this.cookerid = cookerid;
                System.out.println(this.toString() + "was made!");
            }
    
            @Override
            public String toString() {
                return "#" + id + " by " + cookerid;
            }
    
        }
    
        // 汉堡包容器
        class HambergFifo {
            
            List<Hamberg> hambergs = new ArrayList<Hamberg>();// 借助ArrayList来存放汉堡包
            int maxSize = 10;// 指定容器容量
    
            // 放入汉堡
            public <T extends Hamberg> void push(T t) {
                hambergs.add(t);
            }
    
            // 取出汉堡
            public Hamberg pop() {
                Hamberg h = hambergs.get(0);
                hambergs.remove(0);
                return h;
            }
    
            // 判断容器是否为空
            public boolean isEmpty() {
                return hambergs.isEmpty();
            }
    
            // 判断容器内汉堡的个数
            public int size() {
                return hambergs.size();
            }
    
            // 返回容器的最大容量
            public int getMaxSize() {
                return this.maxSize;
            }
        }
     

    接下来我们构造厨师对象:

      
    //厨师对象
        class Cooker implements Runnable {
            // 厨师要面对容器
            HambergFifo pool;
            // 还要面对服务生
            Waiter waiter;
    
            public Cooker(Waiter waiter, HambergFifo hambergStack) {
                this.pool = hambergStack;
                this.waiter = waiter;
            }
    
            // 制造汉堡
            public void makeHamberg() {
                // 制造的个数
                int madeCount = 0;
                // 因为容器满,被迫等待的次数
                int fullFiredCount = 0;
                try {
    
                    while (true) {
                        // 制作汉堡前的准备工作
                        Thread.sleep(1000);
                        if (pool.size() < pool.getMaxSize()) {
                            synchronized (waiter) {
                                // 容器未满,制作汉堡,并放入容器。
                                pool.push(new Hamberg(++madeCount, Thread
                                        .currentThread().getName()));
                                // 说出容器内汉堡数量
                                System.out.println(Thread.currentThread().getName()
                                        + ": There are " + pool.size()
                                        + " Hambergs in all");
                                // 让服务生通知顾客,有汉堡可以吃了
                                waiter.notifyAll();
                                System.out
                                        .println("### Cooker: waiter.notifyAll() :"
                                                + " Hi! Customers, we got some new Hambergs!");
                            }
                        } else {
                            synchronized (pool) {
                                if (fullFiredCount++ < 10) {
                                    // 发现容器满了,停止做汉堡的尝试。
                                    System.out
                                            .println(Thread.currentThread()
                                                    .getName()
                                                    + ": Hamberg Pool is Full, Stop making hamberg");
                                    System.out.println("### Cooker: pool.wait()");
                                    // 汉堡容器的状况使厨师等待
                                    pool.wait();
                                } else {
                                    return;
                                }
                            }
    
                        }
    
                        // 做完汉堡要进行收尾 工作,为下一次的制作做准备。
                        Thread.sleep(1000);
    
                    }
                } catch (Exception e) {
                    madeCount--;
                    e.printStackTrace();
                }
            }
    
            public void run() {
    
                makeHamberg();
    
            }
        }


    接下来,我们构造顾客对象:

       
    class Customer implements Runnable {
            // 顾客要面对服务生
            Waiter waiter;
            // 也要面对汉堡包容器
            HambergFifo pool;
            // 想要记下自己吃了多少汉堡
            int ateCount = 0;
            // 吃每个汉堡的时间不尽相同
            long sleeptime;
            // 用于产生随机数
            Random r = new Random();
    
            public Customer(Waiter waiter, HambergFifo pool) {
                this.waiter = waiter;
                this.pool = pool;
            }
    
            public void run() {
    
                while (true) {
    
                    try {
                        // 取汉堡
                        getHamberg();
                        // 吃汉堡
                        eatHamberg();
                    } catch (Exception e) {
                        synchronized (waiter) {
                            System.out.println(e.getMessage());
                            // 若取不到汉堡,要和服务生打交道
                            try {
                                System.out
                                        .println("### Customer: waiter.wait():"
                                                + " Sorry, Sir, there is no hambergs left, please wait!");
                                System.out.println(Thread.currentThread().getName()
                                        + ": OK, Waiting for new hambergs");
                                // 服务生安抚顾客,让他等待。
                                waiter.wait();
                                continue;
                            } catch (InterruptedException ex) {
                                ex.printStackTrace();
                            }
                        }
                    }
                }
            }
    
            private void eatHamberg() {
                try {
                    // 吃每个汉堡的时间不等
                    sleeptime = Math.abs(r.nextInt(3000)) * 5;
                    System.out.println(Thread.currentThread().getName()
                            + ": I'm eating the hamberg for " + sleeptime
                            + " milliseconds");
    
                    Thread.sleep(sleeptime);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
            private void getHamberg() throws Exception {
                Hamberg hamberg = null;
    
                synchronized (pool) {
                    try {
                        // 在容器内取汉堡
                        hamberg = pool.pop();
    
                        ateCount++;
                        System.out.println(Thread.currentThread().getName()
                                + ": I Got " + ateCount + " Hamberg " + hamberg);
                        System.out.println(Thread.currentThread().getName()
                                + ": There are still " + pool.size()
                                + " hambergs left");
    
                    } catch (Exception e) {
                        pool.notifyAll();
                        System.out.println("### Customer: pool.notifyAll()");
                        throw new Exception(
                                Thread.currentThread().getName()
                                        + ": OH MY GOD!!!! No hambergs left, Waiter![Ring the bell besides the hamberg pool]");
    
                    }
                }
            }
        }

    最后,我们构造汉堡店,让这个故事发生:
    public class HambergShop {

        Waiter waiter 
    = new Waiter();
        HambergFifo hambergPool 
    = new HambergFifo();
        Customer c1 
    = new Customer(waiter, hambergPool);
        Customer c2 
    = new Customer(waiter, hambergPool);
        Customer c3 
    = new Customer(waiter, hambergPool);
        Cooker cooker 
    = new Cooker(waiter, hambergPool);

        
    public static void main(String[] args) {
            HambergShop hambergShop 
    = new HambergShop();
            Thread t1 
    = new Thread(hambergShop.c1, "Customer 1");
            Thread t2 
    = new Thread(hambergShop.c2, "Customer 2");
            Thread t3 
    = new Thread(hambergShop.c3, "Customer 3");
            Thread t4 
    = new Thread(hambergShop.cooker, "Cooker 1");
            Thread t5 
    = new Thread(hambergShop.cooker, "Cooker 2");
            Thread t6 
    = new Thread(hambergShop.cooker, "Cooker 3");
            t4.start();
            t5.start();
            t6.start();
            
    try {
                Thread.sleep(
    10000);
            } 
    catch (Exception e) {
            }

            t1.start();
            t2.start();
            t3.start();
        }
    }

    运行这个程序吧,然后你会看到我们汉堡店的比赛进行的很好,只是不

     

    知道那些顾客是不是会被撑到。。。
    读到这里,有的读者可能会想到前面介绍的重入锁ReentrantLock。
    有的读者会问:如果我用ReentrantLock来代替上面这些例程当中的 synchronized块,是不是也可以呢?感兴趣的读者不妨一试。
    但是在这里,我想提前给出结论,就是,
    如果用ReentrantLock的lock()和unlock()方法代替上面的synchronized块,那么上面这些程序还是要抛出 java.lang.IllegalMonitorStateException异常的,不仅如此,你甚至还会看到线程死锁。原因就是当某个线程调用第三 方对象的wait或者notify方法的时候,并没有进入第三方对象的监视器,于是抛出了异常信息。但此时,程序流程如果没有用finally来处理 unlock方法,那么你的线程已经被lock方法上锁,并且无法解锁。程序在java.util.concurrent框架的语义级别死锁了,你用 JConsole这种工具来检测JVM死锁,还检测不出来。
    正确的做法就是,只使用ReentrantLock,而不使用wait或者notify方法。因为ReentrantLock已经对这种互斥和协作进行了 概括。所以,根据你程序的需要,请单独采用重入锁或者synchronized一种同步机制,最好不要混用。

    1. 线程的等待或者唤醒,并不是让线程调用自己的wait或者notify方法,而是通过调用线程共享对象的wait或者notify方法来实现。
    2. 线程要调用某个对象的wait或者notify方法,必须先取得该对象的监视器。
    3. 线程的协作必须以线程的互斥为前提,这种协作实际上是一种互斥下的协作。
    您可能也喜欢:

     

     

     

    http://android.group.iteye.com/group/wiki/3083-java-sync-communication

  • 相关阅读:
    HDU 2544 最短路
    HDU 3367 Pseudoforest
    USACO 2001 OPEN
    HDU 3371 Connect the Cities
    HDU 1301 Jungle Roads
    HDU 1879 继续畅通工程
    HDU 1233 还是畅通工程
    HDU 1162 Eddy's picture
    HDU 5745 La Vie en rose
    HDU 5744 Keep On Movin
  • 原文地址:https://www.cnblogs.com/jiezzy/p/2658856.html
Copyright © 2011-2022 走看看