多线程循环锁

 循环锁是多线程编程中比较常见的一种编程技巧。其主要的逻辑框架是对需要执行的一段代码进行保护,当保护条件成立时才执行该段代码,而当条件不成立时就让线程等待。

while(!doCondition){

    wait();

}

doSomethingImportant();

 

    这段代码虽然短小精悍,但是在多线程编程中极为有用。这里举个简单的例子以助于理解。比如公司桌上有个盒子,有些人将自己带的吃的放进去,有些人则拿出自 己想吃的东西。这时候其实我们是有这样的约束条件的:盒子不空则能取出东西吃,盒子不满则能向盒子中放东西。这个约束条件就是这里的 doCondition,套用过来就是说盒子空了就要等待直到盒子里有东西才能取出来吃,同样,盒子满了就要等待空出一个位置才能放东西进去。于是把盒子 作为一个资源临界区,我们有如下的代码:

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,会发现开始线程就一直握着锁,醒了判断条件一定不满足,然后 又睡,又醒……它们被放在了死循环中,因为循环条件需要另一个线程持锁完成,可锁却被自己紧紧握着。

posted @ 2016-10-11 15:48  齐楚燕韩赵魏秦  阅读(938)  评论(0编辑  收藏  举报