notify和notifyall这2个唤醒线程API除了唤醒的线程数量不同还有没有其他区别?(答案如下)
来源:互联网 发布:电脑恢复视频软件 编辑:程序博客网 时间:2024/05/16 11:38
【这篇文章内容是存在一些问题的,通过网友提出的建议,进行了一定的修改,不过为了大家看得懂评论内容,原文不做修改,我重新整理了文章发布到了CSDN上面,大家可以通过此链接踏雁寻花进入查看】
当你Google”notify()和notifyAll()的区别”时,会有大片的结果弹出来,(这里先把jdk的javadoc文档那一段撇开不说),所有这些搜索结果归结为等待的线程被唤醒的数量:notify()是唤醒一个, 而notifyall()是唤醒全部.那他们的真正区别是什么呢?
public synchronized void put(Object o) { while ( buf.size() == MAX_SIZE) { wait(); // 如果buffer为Null,就会执行wait方法等待(为了简单,我们省略try/catch语句块) } buf.add(o); notify(); // 通知所有正在等待对象锁的Producer和Consumer(译者注:包括被阻挡在方法外的Producer和Consumer)} public synchronized Object get() { // Y: 这里是C2获取锁的地方(在get方法之前) while ( buf.size() == 0) { wait(); // 如果buffer为Null,就会执行wait方法(为了简单,同样省略try/catch语句块) // X: 这里是C1试图重新获得锁的地方(看下面代码) } Object o = buf.remove(0); notify(); // 通知所有正在等待对象锁的Producer和Consumer(译者注:包括被阻挡在方法外的Producer和Consumer) return o;}
消费者1(C1)进入同步块中,此时buf是空的,所以C1被放入阻塞队列中(译者注:此时被卡在X上面),消费者2(C2)将要进入同步方法中(在Y的上面),但是由于有线程在运行,所以只能被阻挡在Y处的上面。然后,生产者P1将一个对象放入到buf中,又调用notify方法。此时阻塞队列中唯一的线程是C1(译者注:C2不在阻塞队列中),所以C1被唤醒,C1被唤醒之后又开始试图重新获得对象锁,此时C1还在X的上面。
现在的情况是,C1和C2都在试图去获取同步锁,这两个线程会有一个被选择进入方法,另一个则会被堵塞(译者注:C1已经在方法中,不过还是会和C2竞争锁,如果C2获得锁,则C2进入方法执行以下操作,而C1还是继续等待锁;如果C1获得锁,则C1往下执行,而C2还是会被挡在方法外面)。假如C2先获得了对象锁,C1仍然被阻挡着(此时C1还试图在X处获得锁,所以被挡在X上面),C2完成了方法,并释放了锁。现在C1获得了锁。假设这里没有while循环,那么C1就会往下执行,从buf中删除一个对象,但是此时buf中已经没有对象了,因为刚刚C2已经取走了一个对象,如果此时C1执行buf.remove(0),则会报IndexArrayOutOfBoundsException异常。为了防止这样的异常发生,我们在上面用到了while循环,在往下执行之前,判断此时buf的大小是否为0,如果不是0,则往下执行,如果是0,则继续wait()。
那么我们这里引出问题:为什么需要notifyAll?
在上面生产者-消费者这个例子中,看起来我们用notify也能够侥幸成功,因为等待循环的哨兵对于消费者和生产者来说是互斥的。我们不能同时在put方法和get方法都有一个线程wait,如果这种情况允许的话,那么下面的事情就会发生:
buf.size() == 0 AND buf.size() == MAX_SIZE (假设MAX_SIZE不为0)
然而,这样并不好,我们需要使用notifyAll。让我们来看一看原因:
假设 buffer=1(为了更加容易理解),按照下面的步骤执行将会发生死锁。要注意的是:notify可以唤醒任何一个线程,不过JVM不能确定是哪个线程被唤醒,所以,任何一个线程都有被唤醒的可能。另外要注意的是,当多个线程被阻塞在方法外的时候(在试图获得锁),获得锁的顺序也是不确定的。要记住,在任何时候,方法中只能有一个线程存在-在类中任何同步的方法只允许一个线程执行(这个线程要持有对象锁才可以执行)。如果下面的执行顺序发生了的话,就会导致死锁:
第一步:P1放入一个对象到buffer中;
第二步:P2试图put一个对象,此时buf中已经有一个了,所以wait
第三步:P3试图put一个对象,仍然wait
第四步:
- C1试图从buf中获得一个对象;
- C2试图从buf中获得一个对象,但是挡在了get方法外面
- C3试图从buf中获得一个对象,同样挡在了get方法外面
第五步:
- C1执行完get方法,执行notify,退出方法
- notify唤醒了P2,
- 但是C2在P2唤醒之前先进入了get方法,所以P2必须再次获得锁,P2被挡在了put方法的外面,
- C2循环检查buf大小,在buf中没有对象,所以只能wait;
- C3在C2之后,P2之前进入了方法,由于buf中没有对象,所以也wait;
第六步:
- 现在,有P3,C2,C3在waiting;
- 最后P2获得了锁,在buf中放入了一个对象,执行notify,退出put方法;
第七步:
- notify唤醒P3;
- P3检查循环条件,在buf中已经有了一个对象,所以wait;
- 现在没有线程能够notify了,三个线程就会处于死锁状态。
下面是译者分析:
在执行第五步时,即C1执行完get方法后,又执行了notifyAll方法,此时,notifyAll方法会唤醒所有正在等待该锁的线程,那么所有的线程都会处于运行前的准备状态(此时不是wait状态),此时,即使C2在P2(此时P2已经被唤醒,P3也被唤醒,处于准备状态,而不是wait状态)之前先进入了get方法,C2循环检查buf大小,在buf中没有对象,所以进入wait状态;C3在C2之后,P2之前进入方法,由于buf中没有对象,所以也wait;(这里重新分析了一下步骤五发生的情景)
第六步:现在,有C2,C3在waiting,P3在第五步已经被唤醒了,处于准备状态,此时,如果P2获得锁,在buf中放入一个对象,执行notifyAll,又将C2、C3唤醒了;
附:
notify和notifyAll的区别:
notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。
void notify(): 唤醒一个正在等待该对象的线程。
void notifyAll(): 唤醒所有正在等待该对象的线程。
两者的最大区别在于:
notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
notify他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁,此时如果该对象没有再次使用notify语句,即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。
以前大伙看到这两个区别的时候可能感觉到很懵,相信现在应该有些明白了吧,如果还没有搞清楚可以看一下我这里案例的分析:notify发生死锁的情景
原文链接: stackoverflow 翻译: ImportNew.com- 宋 涛
译文链接: http://www.importnew.com/10173.html
- notify和notifyall这2个唤醒线程API除了唤醒的线程数量不同还有没有其他区别?(答案如下)
- 线程间通信 等待唤醒机制 wait notify notifyAll lock Condition唤醒 停止线程interrupt 守护线程setDaemon join yield
- 线程的挂起和唤醒
- 线程的挂起和唤醒
- java 中wait和notify 线程等待和线程唤醒的使用方式 需要借助synchronized
- Java的多线程NotiFyAll()唤醒线程时的顺序问题
- java多线程-线程间通信-示例代码-解决安全问题-等待唤醒机制wait()notify()notifyAll()
- notifyAll()唤醒线程时与什么有关
- Java-线程$等待唤醒机制(wait,notify)
- Java线程之等待wait唤醒notify示例(一)
- 线程的虚假唤醒
- JAVA 线程等待唤醒,wait and notify
- 线程唤醒
- 第二部分 线程的等待和唤醒
- 线程的挂起,唤醒和终止
- 第二部分 线程的等待和唤醒
- java 线程的挂起和唤醒
- MFC线程的挂起、唤醒和终止
- 使用sqlite,在更新操作时每次报错“database is locked”
- 宾馆神秘顾客检测内容及指标体系
- LAJP(Linux Apache Java Php) -- PHP结合JAVA的开发技术
- Kinect开发教程一:OpenNI的安装与开发环境配置
- 93. 中序遍历序列
- notify和notifyall这2个唤醒线程API除了唤醒的线程数量不同还有没有其他区别?(答案如下)
- hdu 1678 Shopaholic
- Tcl lab
- 50个c/c++源代码网站
- 制作动画效果的Button
- android设备管理器接口
- gtk treeview remove row
- RTEMS 网络驱动的一些分析和理解
- 【BUG】关于手动打包、ANT实现的 补充