• 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

  • 相关阅读:
    Spring MVC(1)Spring MVC的初始化和流程以及SSM的实现
    Spring(四)Spring与数据库编程
    MyBatis(4)-- 动态SQL
    MyBatis(3)-- Mapper映射器
    MyBatis(2)-- MyBatis配置mybatis-config.xml
    MyBatis(1)-- MyBatis介绍
    计算机网络(2)-- URL、HTTP、HTTPS、HTML
    计算机网络(1)- TCP
    Shell脚本编程
    和为定值的多个数
  • 原文地址:https://www.cnblogs.com/jiezzy/p/2658856.html
Copyright © 2020-2023  润新知