防止信号的错失

     

 在任务的协作中,是需要任务间的协议,而这份协议,正如之前所说的,正是通过锁来实现。但是,有时候,这协议会错失,就是没有被其他任务正确的接收到。信号的错失会导致错误,所以,我们要为避免信号错失而做出努力,因为信号的错失是可能导致出现死锁的,只要是使用锁,就要避免出现死锁这种局面。这问题的解决方案就是防止在唤醒条件上产生竞争,因为很多时候的错失都是因为对唤醒条件的竞争,如:
T1:
       synchronized(sharedMonitor){
           <setup condition for T2>
            sharedMonitor.notify();
        }
     
T2:
    while(someCondition){
         synchronized(sharedMonitor){
            sharedMonitor.wait();
         }
    }
         <setup condition for T2>是防止T2 调用wait()的一个动作,当然前提是T2还没有调用wait()。这个的具体实现可以不用管。假设T2对someCondition求值并发现其为true,在T2中的while循环中,线程调度器可能切换到T1,而T1将执行其设置,然后调用notify(),当T2得以继续执行时,此时对于T2来说,时机已经太晚了,以至于不能意识到这个条件已经发生变化,因此会盲目进入wait(),此时notify()将错失,而T2也将无限的等待这个已经发送过的信号,从而产生死锁。大家可能还是对这个<setup condition for T2>的具体实现有疑问,到底怎么实现,其实我们可以这样想,因为线程调度机制到底怎么处理,我们并不清楚,所以实际处理中是可能会出现上面的情况,就算我们并没有什么特别的举动。解决的做法很简单,简单到只需调换一下顺序就行。就是这样:
       synchronized(sharedMonitor){
           while(someCondition){
                sharedMonitor.wait();
            }
       }
      为什么这样就可以消除对唤醒条件的竞争呢?因为现在当T1首先执行时,当控制返回T2时,它将发现条件发生变化,从而不会进入wait(),反过来,如果T2首先执行,那它将进行wait(),并且稍后会由T1唤醒。
        好了,前面都是《Thinking in java》中的内容,是的,其实基本上都是一样的,之所以这样贴出来,是想要和大家一起探讨这部分的内容。这时,我们都会有一个疑问,时机,那个错失的时机到底在哪?两个代码的区别就在于唤醒条件的出现顺序。让我们顺着这样说下去,前面所讲的,在T2中的while循环中,线程调度器可能切换到T1,这完全是有可能的,因为T1根本就没有唤醒条件,所以它是可以进行的,而这个T1中恰好有防止T2调用wait()的动作,所以T2可能就无法在这个本该唤醒的时候没有唤醒,等到T1调用notify()唤醒的时候就可能有点晚了。这就是信号的错失。那么现在呢,T1先执行,使得唤醒条件发生变化,于是也就不会进入wait()。大家注意到没有,关键就是T1的执行能够对T2的影响是发生在哪个部分,一旦进入方法里面,是不可能会有其他的任务在执行,所以这就能确保信号不会错失。
       所以,大家是否意识到,合理的循环结构是会对我们的程序产生多么大的影响啊,所以设计循环时是要谨慎的。
原文地址:https://www.cnblogs.com/wenjiang/p/2660543.html