本文主要是介绍notify、notifyAll、wait思考,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1、在java中,每个对象都有两个池,锁(monitor)池和等待池,每个对象都能够被作为“监视器monitor”——指一个拥有一个独占锁
wait()/notify()方法定义在Object类中。如果线程要调用对象的wait()方法,必须首先获得该对象的监视器锁,调用wait()之后,当前线程又立即释放掉锁,线程随后进入WAIT_SET(等待池)中。如果线程要调用对象的notify()/notifyAll()方法,也必须先获得对象的监视器锁,调用方法之后,立即释放掉锁,然后处于Wait_set的线程被转移到Entry_set(等锁池)中,去竞争锁资源。The Winner Thread,也就是成功获得了对象的锁的线程,就是对象锁的拥有者,会进入runnable状态。由于需要获得锁之后才能够调用wait()/notify()方法,因此必须将它们放到同步代码块中。
锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.
下面是动力节点课程中对于锁池和等待池的说明:
在java中,每个对象都有两个池,锁池和等待池。Java平台中,因为有内置锁的机制,每个对象都可以承担锁的功能。Java虚拟机会为每个对象维护两个“队列”(姑且称之为“队列”,尽管它不一定符合数据结构上队列的“先进先出”原则):一个叫Entry Set(入口集),另外一个叫Wait Set(等待集)。对于任意的对objectX,objectX的Entry Set用于存储等待获取objectX这个锁的所有线程,也就是传说中的锁池,objectX的Wait Set用于存储执行了objectX.wait()/wait(long)的线程,也就是等待池。
下面我们来看锁池和等待池的具体介绍:
锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池。
设objectX是任意一个对象,假设有线程A、B、C同时申请objectX这个对象锁,那么由于任意一个时刻只有一个线程能够获得(占用/持有)这个锁,因此除了胜出(即获得了锁)的线程(这里假设是B)外,其他线程(这里就是A和C)都会被暂停(线程的生命周期状态会被调整为BLOCKED)。这些因申请锁而落选的线程就会被存入objectX对应的锁池之中。当objectX被其持有线程(这里就是B)释放时,锁池中的一个任意(注意是“任意”,而不一定是锁池中等待时间最长或者最短的)线程会被唤醒(即线程的生命周期状态变更为RUNNABLE)。这个被唤醒的线程会与其他活跃线程(即不处于锁池之中,且线程的生命周期状态为RUNNABLE的线程)再次抢占objectX。这时,被唤醒的线程如果成功申请到objectX,那么该线程就从锁池中移除。否则,被唤醒的线程仍然会停留在锁池中,并再次被暂停,以等待下次申请锁的机会。
如果有个线程执行了objectX.wait(),那么该线程就会被暂停(线程的生命周期状态会被调整为Waiting),并且会释放掉objectX锁,然后被存入objectX的等待池之中。此时,该线程就被称为objectX的等待线程。当其他线程执行了objectX.notify()/notifyAll()时,等待池中的一个(或者多个,取决于被调用的是notify还是notifyAll方法)任意(注意是“任意”,而不一定是等待池中等待时间最长或者最短的)等待线程会被唤醒,这些被唤醒的线程会被放到锁池中,会与锁池中已经存在的线程以及其他(可能的)活跃线程共同参与抢夺objectX。至于代码中到底是使用notify还是notifyAll方法,这个要根据实际情况来分析。
2、wait() ,notifyAll(),notify() 三个方法都是Object类中的方法.
3、notify发生死锁的情景
package com.tyxh.block;class OutTurn {private boolean isSub = true;private int count = 0;public synchronized void sub() {try {while (!isSub) {this.wait();}System.out.println("sub ---- " + count);isSub = false;this.notify();} catch (Exception e) {e.printStackTrace();}count++;}public synchronized void main() {try {while (isSub) {this.wait();}System.out.println("main (((((((((((( " + count);isSub = true;this.notify();} catch (Exception e) {e.printStackTrace();}count++;}
}
package com.tyxh.block;public class LockDemo {public static void main(String[] args) {// System.out.println("lock");final OutTurn ot = new OutTurn();for (int j = 0; j < 100; j++) {new Thread(new Runnable() {public void run() {// try {// Thread.sleep(10);// } catch (InterruptedException e) {// e.printStackTrace();// }for (int i = 0; i < 5; i++) {ot.sub();}}}).start();new Thread(new Runnable() {public void run() {// try {// Thread.sleep(10);// } catch (InterruptedException e) {// e.printStackTrace();// }for (int i = 0; i < 5; i++) {ot.main();}}}).start();}}
}
解释一下原因:
OutTurn类中的sub和main方法都是同步方法,所以多个调用sub和main方法的线程都会处于阻塞状态,等待一个正在运行的线程来唤醒它们。下面分别分析一下使用notify和notifyAll方法唤醒线程的不同之处:
上面的代码使用了notify方法进行唤醒,而notify方法只能唤醒一个线程,其它等待的线程仍然处于wait状态,假设调用sub方法的线程执行完后(即 System. out .println( "sub ---- " + count ) 执行完之后),所有的线程都处于等待状态,此时在sub方法中的线程执行了isSub=false语句后又执行了notify方法,这时如果唤醒的是一个sub方法的调度线程,那么while循环等于true,则此唤醒的线程也会处于等待状态,此时所有的线程都处于等待状态,那么也就没有了运行的线程来唤醒它们,这就发生了死锁。
如果使用notifyAll方法来唤醒所有正在等待该锁的线程,那么所有的线程都会处于运行前的准备状态(就是sub方法执行完后,唤醒了所有等待该锁的状态,注:不是wait状态),那么此时,即使再次唤醒一个sub方法调度线程,while循环等于true,唤醒的线程再次处于等待状态,那么还会有其它的线程可以获得锁,进入运行状态。
总结:notify方法很容易引起死锁,除非你根据自己的程序设计,确定不会发生死锁,notifyAll方法则是线程的安全唤醒方法。
4、java 为什么wait(),notify(),notifyAll()必须在同步(Synchronized)方法/代码块中调用
先回答问题:
(1)为什么wait()必须在同步(Synchronized)方法/代码块中调用?
答:调用wait()就是释放锁,释放锁的前提是必须要先获得锁,先获得锁才能释放锁。
(2)为什么notify(),notifyAll()必须在同步(Synchronized)方法/代码块中调用?
答:notify(),notifyAll()是将锁交给含有wait()方法的线程,让其继续执行下去,如果自身没有锁,怎么叫把锁交给其他线程呢;(本质是让处于入口队列的线程竞争锁)
下面是正文(详细解释):
之前一直谨记老师教的wait(),notify(),notifyAll()必须要在Synchronized关键中使用,不得其解,现在研究了一下,终于明白了。
首先,要明白,每个对象都可以被认为是一个"监视器monitor",这个监视器由三部分组成(一个独占锁,一个入口队列,一个等待队列)。注意是一个对象只能有一个独占锁,但是任意线程线程都可以拥有这个独占锁。
对于对象的非同步方法而言,任意时刻可以有任意个线程调用该方法。(即普通方法同一时刻可以有多个线程调用)
对于对象的同步方法而言,只有拥有这个对象的独占锁才能调用这个同步方法。如果这个独占锁被其他线程占用,那么另外一个调用该同步方法的线程就会处于阻塞状态,此线程进入入口队列。
若一个拥有该独占锁的线程调用该对象同步方法的wait()方法,则该线程会释放独占锁,并加入对象的等待队列;(为什么使用wait()?希望某个变量被设置之后再执行,notify()通知变量已经被设置。)
某个线程调用notify(),notifyAll()方法是将等待队列的线程转移到入口队列,然后让他们竞争锁,所以这个调用线程本身必须拥有锁。
这篇关于notify、notifyAll、wait思考的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!