多线程循环锁
while(!doCondition){ wait(); } doSomethingImportant(); |
public class Box{
...... public synchronized void put(Object something){ while(isFull){//can't put try{ wait(); } catch(InterruptedException){ } } doPut(something); notifyAll(); }
public synchronized Object get(){ while(isEmpty){ try{ wait(); } catch(InterruptedException){ } } Object something = doGet(); notifyAll(); return something; } ...... } |
我们来分析一下get,如果盒子是空的话线程就一直等待,而要线程继续执行循环之外的语句其实有两个条件,条 件一:有其它线程将其唤醒,条件二:唤醒后盒子至少有一件东西在里面。这里用这个while循环保证了这两个条件。之后才是事实上的从盒子去东西出来,然 后有一个notifyAll(),在这里的意思是对那些准备put却因为盒子满的线程而做的,将所有线程唤醒,一旦那些put线程得到控制权那么一定满足 两个条件第一是被唤醒了第二至少有一个空间能放东西。循环锁保证了这一整个过程的安全性,因此这是个非常重要的技巧。
试想,如果while改成if还安全吗?当然不安全了,假设当前存在几个线程同时在get上 等待(这是可能的,假设盒子已为空),这时一个线程put并唤醒了全部等待的线程,第一个线程没有检查就开始执行doGet()(因为用了if),庆幸的 是它是正确的,但第二个线程同样没检查就doGet(),由于被第一个线程把盒子最后一件物品也取走了,所以第二个线程是不可能取到东西的,这时就会报错 了,因此换成if是不安全的,这也正式循环锁的意义锁在。
那如果将try{}catch提到循环外呢?这个程序还是安全的吗?答案仍然是否定的。解释 其实同上面一样,还记得我强调过InterruptedException这个异常,一旦被唤醒,是从catch后的下一个语句开始执行,如果try在 while里,catch后的下一个语句其实是while的条件判断;如果try在while外面,catch的下一个语句就是直接执行doGet,其实 就成了刚刚那种情况。
还有一点,循环中的wait也不能变成sleep,之前特别强调过wait在进入之前会获得对象锁,而进入后就释放了,因此能够给其他线程进入这段代码的 机会,而sleep则是在睡觉过程中还死握着锁不放,不妨将上例两个wait换成sleep,会发现开始线程就一直握着锁,醒了判断条件一定不满足,然后 又睡,又醒……它们被放在了死循环中,因为循环条件需要另一个线程持锁完成,可锁却被自己紧紧握着。