黑马程序员技术交流社区

标题: 关于wait方法的两点小心的,跟大家分享一下 [打印本页]

作者: Porsche911    时间: 2014-11-22 19:34
标题: 关于wait方法的两点小心的,跟大家分享一下
本帖最后由 Porsche911 于 2014-11-24 11:06 编辑

本人零基础,所以下面要说的内容可能对一部分人来说,比较可笑,还望大家轻拍。主要目的就是希望同样是零基础的朋友少走些弯路。
1. 释放锁
    意思就是,当某个线程通过wait方法进入冻结状态时,会同时释放该线程持有的锁。我印象中这个小知识点,毕老师貌似没有强调过,不然我一定会记录下来的,因为这个特点是与同样释放执行权和执行资格的sleep方法最大的区别。
       实际上,如果wait方法没有释放锁的特点,等待唤醒机制也就不复存在了,具体代码参考毕老师的视频这里不再赘述了,只是给大家提个醒。
2. 谁调用wait,就释放哪个锁对象
    我曾经有过一个错误的想法:以为线程一旦进入冻结,就会释放它所持有的全部的锁。但实际情况并不是这样,而这一错误想法导致我竟然写出下面这样愚蠢的代码:

//共享资源类
class Resource
{
       //商品名
       private String name;
       //商品数量
       private int count = 1;
       //标记
       private boolean flag =false;

       //两个锁对象
       private Object obj_pro =new Object();
       private Object obj_con =new Object();

       //生产方法,为方便观察几乎为每一步都添加了输出语句
       public void produce(Stringname)
       {
              System.out.println(Thread.currentThread().getName()+"------------pro1");
              //第一层同步代码块,使用obj_con锁
              synchronized(obj_con)
              {
                     System.out.println(Thread.currentThread().getName()+"------------pro2");
                     //第二层同步代码块,使用obj_pro锁
                     synchronized(obj_pro)
                     {
                            System.out.println(Thread.currentThread().getName()+"------------pro3");
                            //当标记为真时,表明生产了一个商品,等待
                            while(flag)
                            {
                                   System.out.println(Thread.currentThread().getName()+"------------pro4");
                                   //通过condition_pro对象只冻结生产者线程
                                   try{obj_pro.wait();}catch(InterruptedException  e){}
                            }

                            this.name= name+"--"+count++;

                            System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
                            flag =true;

                            //只唤醒消费者线程
                            obj_con.notify();
                            System.out.println(Thread.currentThread().getName()+"------------pro5");
                     }
                     System.out.println(Thread.currentThread().getName()+"------------pro6");
              }
              System.out.println(Thread.currentThread().getName()+"------------pro7");
       }
       //消费方法
       public void consume()
       {
              System.out.println(Thread.currentThread().getName()+"------------con1");
              synchronized(obj_con)
              {
                     System.out.println(Thread.currentThread().getName()+"------------con2");
                     synchronized(obj_pro)
                     {
                            System.out.println(Thread.currentThread().getName()+"------------con3");
                            while(!flag)
                            {
                                   System.out.println(Thread.currentThread().getName()+"------------con4");
                                   //冻结消费者线程
                                   try{obj_con.wait();}catch(InterruptedExceptione){}
                            }

                            System.out.println(Thread.currentThread().getName()+"..........消费者.........."+this.name);
                            flag =false;

                            //唤醒生产者线程
                            obj_pro.notify();

                            System.out.println(Thread.currentThread().getName()+"------------con5");
                     }
                     System.out.println(Thread.currentThread().getName()+"------------con6");
              }
              System.out.println(Thread.currentThread().getName()+"------------con7");
       }
}
//生产者
class Producer implements Runnable
{
       private Resource res;
       Producer(Resource res)
       {
              this.res = res;
       }

       public void run()
       {
              while(true)
              {
                     res.produce("+商品+");
              }
       }
}
//消费者
class Consumer implements Runnable
{
       private Resource res;
       Consumer(Resource res)
       {
              this.res = res;
       }

       public void run()
       {
              while(true)
              {
                     res.consume();
              }
       }
}
       上述代码是对生产者消费者例子的简单修改,由于代码怎么也上传不上去,只能作为文字上传了,而且因为字节限制就去掉主类了,不影响大家理解。我在操作共享资源的代码上嵌套了两层同步代码块,并且使用了两个不同的锁。多次运行结果就是死锁,为了找出问题原因,我在同步代码块内每执行一步就打印一行字符串,并且在CSDN论坛向大神请教,最终得出的结论是:通过哪个锁对象调用的wait方法,该线程冻结以后,就只释放哪个锁,而不是全部释放。现在想想这也是非常合情合理的,因此,通常的运行结果就是这样的:

Thread-1------------pro1
Thread-2------------con1
Thread-3------------ con1
Thread-0------------pro1
Thread-1------------pro2
Thread-1------------pro3
Thread-1…生产者…+商品+--1
Thread-1------------pro5
Thread-1------------pro6
Thread-1------------pro7
Thread-0------------pro2
Thread-1------------pro1
Thread-0------------pro3
Thread-0------------pro4

       一开始所有线程都等在第一层同步代码外面,此时CPU将执行权分配给了1号线程(生产者),令其完整执行了代码(生产了一个商品),之后CPU将执行权迅速分配给了0号线程,判断标记后进入冻结,而0号线程的冻结动作是由obj_pro锁发起的,所以仅仅释放了obj_pro锁,并继续持有obj_con锁,这就导致在此期间其他线程一直止步于第一层同步代码。最后就是死锁。
       再给大家一个好的建议:向上面的代码那样,每执行一步就打印输出,是一种非常好的寻找问题原因的测试方法,这也是毕老师推荐过的,它可以帮助你快速而准确的找到问题所在,但具体问题的原因还是需要查阅资料或者咨询大神。
       最后祝大家周末愉快~~~

作者: sk0806    时间: 2014-11-22 20:04
论坛可以插入代码,符号就是那个“<>”这个图标,加油,零基础,一样可以成大牛
作者: qq8921310    时间: 2014-11-22 20:25
第一:关于wait  和 sleep 毕老师说过,.
第二:notify 是 按顺序唤醒线程池中被冻结的线程.唤醒全部是notifyAll.并不是谁调用wait就唤醒谁.
作者: Porsche911    时间: 2014-11-23 16:47
sk0806 发表于 2014-11-22 20:04
论坛可以插入代码,符号就是那个“”这个图标,加油,零基础,一样可以成大牛 ...

是可以插入啊,但是每次只能显示一小部分代码,后面一大段都不显示,试了好几次,就放弃了。。。
作者: Porsche911    时间: 2014-11-23 16:49
qq8921310 发表于 2014-11-22 20:25
第一:关于wait  和 sleep 毕老师说过,.
第二:notify 是 按顺序唤醒线程池中被冻结的线程.唤醒全部是notifyA ...

我希望你能仔细看完,并且理解我表达的意思以后再发表看法,好吗?




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2