任务不仅是只有竞争的关系,还有协作。所谓的协作,就是一般意义上的协作,多个任务同时完成同一个问题的各个部分。所以,现在问题已经不是任务间的干涉,而是任务间的协调。因为在这类问题中,某些部分必须在其他部分被解决前解决,而且,在这些任务中,某些是可以并发执行的。某些甚至是所有任务都结束后进行,就像建房子一样,总得建完地基后才能开始往上建吧。
所以,任务的协作的关键就是任务间的协议。怎样才能实现这种协议呢?还是使用互斥,也就是锁。没错,你没有看错,就是使用锁来实现协作。为什么可以呢?明明协作与竞争在字面上来说,应该是相反的啊,但是基本构成是可以一样的啊。因为锁能够确保只有一个任务可以响应某个信号,就是可以消除任何可能的竞争,在这样的基础上,我们就可以将任务自身挂起,直到某个外部条件发生变化,表示是时候让这个任务开工了。这样的实现是由Object的wait()和notify()来完成的。
wait()可以使我们等待某个条件发生变化,而改变这个条件超出了当前方法的控制能力。通常,这种条件由另一个任务来改变。但是,这里要注意,不能让你的这个任务陷入忙等待中,就是在测试这个条件时,不断进行空循环。因此,wait()会将这个任务挂起,只有在notify()或notifyAll()发生时,即发生了唤醒条件,这个任务就会被唤醒并且去检查这个条件。在任务的加锁机制这部分,我们知道,调用sleep()和yield()时,锁并没有被释放,但是wait()却大大不同,它会释放锁,这就使得其他synchronized方法可以获得该锁。这就是为什么利用锁就可以达到热任务的协作的关键,因为wait()方法不仅挂起线程,而且释放锁,然后等到notify()或notifyAll()捕捉住唤醒条件,开始执行下一步。我们在使用wait()时,习惯用法就是用一个检查唤醒条件的while循环来包围wait()。这点是非常重要的,为什么呢?原因有下面几个方面:
1.我们可能有多个任务出于相同的原因在等待同一个锁,而第一个唤醒任务可能会改变这种状况,那么这时我们应该怎么做呢?就是将这个任务挂起,防止其造成这种现象,因为它只需要执行到它满足唤醒条件时该做的事情就行,其他的事情不需要现在做,自然会有其他唤醒条件,所以,这个循环就能使得当它完成工作时被挂起;
2.在这个任务从其wait()中被唤醒的时刻,有可能会有某个其他的任务已经做出了改变,从而使得这个任务在此时不能执行,或者执行其操作已显得无关紧要,所以必须时时检查唤醒条件,在这情况下就应该马上挂起;
3.可能某些任务出于不同的唤醒条件在等待你的对象上的锁,这时就要看看唤醒条件是否已经改变,如果改变,就应该挂起释放锁,而且,注意,这时应该是使用notifyAll()。
当然,只要有认真看完上面的内容的,就会产生一个疑问,就是notify()和notifyAll()有什么区别?notifyAll()正如它后面的all,表示唤醒所有正在等待的任务,这句话是不完整的,真正的意思是,它可以唤醒所有等待调用notifyAll()的任务所持有的锁的任务。那么什么时候该使用notify(),什么时候该使用notifyAll()呢?一般情况下,使用notify()而不是notifyAll()是一种优化,因为不用唤醒所有任务,但是我们必须保证一点,就是我们唤醒的是正确的任务,可是啊,如果有多个任务在等待不同的条件,我们又怎能知道我们唤醒的是正确的任务?因为使用notify()只能唤醒一个任务!所以如果无法保证我们唤醒的是一个正确的任务,那么请选择notifyAll(),就像上面第三条所说的情况,你要唤醒的是唤醒条件不同的各个任务,那么就一起唤醒吧。