`
Jack-chen
  • 浏览: 69405 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java 多线程同步问题的探究(四、协作,互斥下的协作——Java多线程协作(wait、notify、notifyAll))

    博客分类:
  • Java
阅读更多
Java监视器支持两种线程:互斥和协作。

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

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

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

在JVM中,此种监视器被称为等待并唤醒监视器。
转载注明出处:http://x- spirit.iteye.com/、http: //www.blogjava.net/zhangwei217245/
在这种监视器中,一个已经持有该监视器的线程,可以通过调用监视对象的wait方法,暂停自身的执行,并释放监视器,自己进入一个等待区,直到监视器内的其他线程调用了监视对象的notify方法。当一个线程调用唤醒命令以后,它会持续持有监视器,直到它主动释放监视器。而这之后,等待线程会苏醒,其中的一个会重新获得监视器,判断条件状态,以便决定是否继续进入等待状态或者执行监视区域,或者退出。

请看下面的代码:

   1. public class NotifyTest {
   2.     private  String flag = "true";
   3. 
   4.     class NotifyThread extends Thread{
   5.         public NotifyThread(String name) {
   6.             super(name);
   7.         }
   8.         public void run() {     
   9.             try {
  10.                 sleep(3000);//推迟3秒钟通知
  11.             } catch (InterruptedException e) {
  12.                 e.printStackTrace();
  13.             }
  14.             
  15.                 flag = "false";
  16.                 flag.notify();
  17.         }
  18.     };
  19. 
  20.     class WaitThread extends Thread {
  21.         public WaitThread(String name) {
  22.             super(name);
  23.         }
  24. 
  25.         public void run() {
  26.             
  27.                 while (flag!="false") {
  28.                     System.out.println(getName() + " begin waiting!");
  29.                     long waitTime = System.currentTimeMillis();
  30.                     try {
  31.                         flag.wait();
  32.                     } catch (InterruptedException e) {
  33.                         e.printStackTrace();
  34.                     }
  35.                     waitTime = System.currentTimeMillis() - waitTime;
  36.                     System.out.println("wait time :"+waitTime);
  37.                 }
  38.                 System.out.println(getName() + " end waiting!");
  39.             
  40.         }
  41.     }
  42. 
  43.     public static void main(String[] args) throws InterruptedException {
  44.         System.out.println("Main Thread Run!");
  45.         NotifyTest test = new NotifyTest();
  46.         NotifyThread notifyThread =test.new NotifyThread("notify01");
  47.         WaitThread waitThread01 = test.new WaitThread("waiter01");
  48.         WaitThread waitThread02 = test.new WaitThread("waiter02");
  49.         WaitThread waitThread03 = test.new WaitThread("waiter03");
  50.         notifyThread.start();
  51.         waitThread01.start();
  52.         waitThread02.start();
  53.         waitThread03.start();
  54.     }
  55. 
  56. } 

转载注明出处:http://x- spirit.iteye.com/、http: //www.blogjava.net/zhangwei217245/
这段代码启动了三个简单的wait线程,当他们处于等待状态以后,试图由一个notify线程来唤醒。
转载注明出处:http://x- spirit.iteye.com/、http: //www.blogjava.net/zhangwei217245/
运行这段程序,你会发现,满屏的java.lang.IllegalMonitorStateException,根本不是你想要的结果。
转载注明出处:http://x- spirit.iteye.com/、http: //www.blogjava.net/zhangwei217245/
请注意以下几个事实:
   1. 任何一个时刻,对象的控制权(monitor)只能被一个线程拥有。
   2. 无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权(monitor)。
   3. 如果在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException异常。
   4. JVM基于多线程,默认情况下不能保证运行时线程的时序性。

也就是说,当线程在调用某个对象的wait或者notify方法的时候,要先取得该对象的控制权,换句话说,就是进入这个对象的监视器。
转载注明出处:http://x- spirit.iteye.com/、http: //www.blogjava.net/zhangwei217245/
通过前面对同步的讨论,我们知道,要让一个线程进入某个对象的监视器,通常有三种方法:
转载注明出处:http://x- spirit.iteye.com/、http: //www.blogjava.net/zhangwei217245/
1: 执行对象的某个同步实例方法
2: 执行对象对应的同步静态方法
3: 执行对该对象加同步锁的同步块

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

于是我们将上面的wait和notify方法调用包在同步块中。

   1.             synchronized (flag) {
   2.                 flag = "false";
   3.                 flag.notify();
   4.             } 

转载注明出处:http://x- spirit.iteye.com/、http: //www.blogjava.net/zhangwei217245/
   1.             synchronized (flag) {
   2.                 while (flag!="false") {
   3.                     System.out.println(getName() + " begin waiting!");
   4.                     long waitTime = System.currentTimeMillis();
   5.                     try {
   6.                         flag.wait();
   7.                     } catch (InterruptedException e) {
   8.                         e.printStackTrace();
   9.                     }
  10.                     waitTime = System.currentTimeMillis() - waitTime;
  11.                     System.out.println("wait time :"+waitTime);
  12.                 }
  13.                 System.out.println(getName() + " end waiting!");
  14.             } 



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

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

1. private   String flag[] = {"true"};


   1.         synchronized (flag) {
   2.             flag[0] = "false";
   3.             flag.notify();
   4.         } 



   1.                  synchronized (flag) {
   2.                 flag[0] = "false";
   3.                 flag.notify();
   4.             }synchronized (flag) {
   5.                 while (flag[0]!="false") {
   6.                     System.out.println(getName() + " begin waiting!");
   7.                     long waitTime = System.currentTimeMillis();
   8.                     try {
   9.                         flag.wait();
  10.                         
  11.                     } catch (InterruptedException e) {
  12.                         e.printStackTrace();
  13.                     } 


运行这个程序,看不到异常了。但是仔细观察结果,貌似只有一个线程被唤醒。利用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]");

                }
            }
        }
    }
转载注明出处:http://x- spirit.iteye.com/、http: //www.blogjava.net/zhangwei217245/
最后,我们构造汉堡店,让这个故事发生:
转载注明出处:http://x- spirit.iteye.com/、http: //www.blogjava.net/zhangwei217245/
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();
    }
}
运行这个程序吧,然后你会看到我们汉堡店的比赛进行的很好,只是不


知道那些顾客是不是会被撑到。。。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics