本帖最后由 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锁,这就导致在此期间其他线程一直止步于第一层同步代码。最后就是死锁。 再给大家一个好的建议:向上面的代码那样,每执行一步就打印输出,是一种非常好的寻找问题原因的测试方法,这也是毕老师推荐过的,它可以帮助你快速而准确的找到问题所在,但具体问题的原因还是需要查阅资料或者咨询大神。 最后祝大家周末愉快~~~ |