黑马程序员技术交流社区
标题: 关于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
是可以插入啊,但是每次只能显示一小部分代码,后面一大段都不显示,试了好几次,就放弃了。。。
作者: Porsche911 时间: 2014-11-23 16:49
我希望你能仔细看完,并且理解我表达的意思以后再发表看法,好吗?
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |