如果有人在谷歌上搜索“notify()和notifyAll()之间的区别”,那么会弹出很多解释(将javadoc段落分开)。这一切归结为被唤醒的等待线程的数量:一个在notify()中,另一个在notifyAll()中
但是(如果我正确理解了这些方法之间的区别),只有一个线程始终被选择用于进一步的监视器采集;在第一种情况下,是由VM选择的,在第二种情况下是由系统线程调度器选择的。程序员不知道这两种方法(在一般情况下)的确切选择过程
<a href=”之间的有用的区别是什么http://download.oracle.com/javase/6/docs/api/java/lang/Object.html#notify%28%29“rel=”noreferrer“>notify()和notifyAll()然后呢?我错过什么了吗
显然,notify唤醒(任何)等待集中的一个线程,notifyAll唤醒等待集中的所有线程。下面的讨论应该可以澄清任何疑问notifyAll应在大部分时间使用。如果您不确定要使用哪个,请使用notifyAll。请参阅下面的说明
仔细阅读并理解。如果您有任何问题,请给我发电子邮件
看看producer/consumer(假设是一个ProducerConsumer类,有两个方法)。它已损坏(因为它使用了notify)-是的,它可能会工作-即使在大多数情况下,但也可能导致死锁-我们将了解原因:
公共同步作废put(对象o){
while(buf.size()=最大大小){
wait();//在缓冲区已满时调用(为简洁起见,try/catch已删除)
}
buf.添加(o);
notify();//在有任何getter或putter等待时调用
}
公共同步对象get(){
//Y:这是C2尝试获取锁的地方(即在方法的开头)
而(buf.size()==0){
wait();//在缓冲区为空时调用(为简洁起见,try/catch已删除)
//X:这是C1尝试重新获取锁的位置(见下文)
}
对象o=buf.remove(0);
notify();//如果有任何getter或putter等待调用
返回o;
}
首先,
为什么我们需要一个围绕等待的while循环?
我们需要一个while循环,以防出现这种情况:
消费者1(C1)进入同步块且缓冲区为空,因此C1被放入等待集中(通过wait调用)。使用者2(C2)即将进入同步方法(在上面的点Y处),但生产者P1将对象放入缓冲区,然后调用notify。唯一等待的线程是C1,因此它被唤醒,现在尝试在点X(上图)重新获取对象锁
现在C1和C2正试图获取同步锁。其中一个(非确定性)被选中并进入方法,另一个被阻止(不是等待,而是被阻止,试图获取方法的锁)。假设C2先得到锁。C1仍在阻塞(试图获取X处的锁)。C2完成该方法并释放锁。现在,C1获得锁。猜猜看,幸运的是,我们有一个while循环,因为C1执行循环检查(保护),并且被阻止从缓冲区中删除一个不存在的元素(C2已经得到了它!)。如果我们在期间没有,当C1试图从缓冲区中删除第一个元素时,我们会得到一个IndexArrayOutOfBoundsException
现在,
好的,现在为什么我们需要通知所有人?
在上面的生产者/消费者示例中,我们似乎可以通过notify逃脱处罚。看起来是这样的,因为我们可以证明生产者和消费者的等待循环上的防护是相互排斥的。也就是说,看起来我们不能让线程在put方法和get方法中等待,因为要实现这一点,必须满足以下条件:
buf.size()==0和buf.size()==MAX\u size(假设MAX\u size不是0)
但是,这还不够好,我们需要使用notifyAll。让我们看看为什么
假设我们有一个大小为1的缓冲区(以使示例易于理解)。以下步骤导致我们陷入僵局。请注意,每当使用notify唤醒线程时,JVM都可以不确定地选择它—也就是说,任何等待的线程都可以被唤醒。还请注意,当多个线程在进入方法时阻塞(即尝试获取锁)时,获取顺序可能是不确定的。还请记住,一个线程在任何时候只能在其中一个方法中-同步方法只允许一个线程执行(即持有类中任何(同步)方法的锁)。如果发生以下事件序列-死锁结果:
步骤1:
-P1将1个字符放入缓冲区
第二步:
-P2尝试put-检查等待循环-已经是字符-等待
第三步:
-P3尝试put-检查等待循环-已经是字符-等待
第4步:
-C1尝试获取1个字符
-C2尝试在进入get方法时获取1个字符块
-C3尝试在get方法的条目上获取1个字符块
第五步:
-C1正在执行get方法-获取字符,调用notify,退出方法
-notify唤醒P2
-但是,C2在P2可以之前进入方法(P2必须重新获得锁),因此P2在进入put方法时会阻塞
-C2检查等待循环,缓冲区中没有更多字符,因此等待
-C3在C2之后进入方法,但在P2之前,检查等待循环,缓冲区中不再有字符,所以等待
第6步:
-现在:P3、C2和C3正在等待
-最后P2获取锁,将一个字符放入缓冲区,调用notify,退出方法
第7步:
-P2的通知唤醒P3(记住任何线程都可以被唤醒)
-P3检查等待循环条件,缓冲区中已经有一个字符,因此等待。
-没有更多线程调用NOTIFY,三个线程永久挂起
解决方案:将生产者/消费者代码(如上)中的notify替换为notifyAll