【多线程】线程间协作:wait、notify、notifyAll

出处链接:https://www.zhihu.com/question/37601861 http://wiki.jikexueyuan.com/project/java-concurrency/collaboration-between-threads.html  https://blog.csdn.net/emailed/article/details/4689220

在java中,每个对象都有两个池,锁(monitor)池和等待池

wait() ,notifyAll(),notify() 三个方法都是Object类中的方法.

锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。

等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.

在 Java 中,可以通过配合调用 Object 对象的 wait() 方法和 notify()方法或 notifyAll() 方法来实现线程间的通信。在线程中调用 wait() 方法,将阻塞等待其他线程的通知(其他线程调用 notify() 方法或 notifyAll() 方法),在线程中调用 notify() 方法或 notifyAll() 方法,将通知其他线程从 wait() 方法处返回。

Object 是所有类的超类,它有 5 个方法组成了等待/通知机制的核心:notify()、notifyAll()、wait()、wait(long)和 wait(long,int)。在 Java 中,所有的类都从 Object 继承而来,因此,所有的类都拥有这些共有方法可供使用。而且,由于他们都被声明为 final,因此在子类中不能覆写任何一个方法。

这里详细说明一下各个方法在使用中需要注意的几点。

wait()

public final void wait() throws InterruptedException,IllegalMonitorStateException

该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用 wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用 wait()方法。进入 wait()方法后,当前线程释放锁。在从 wait()返回前,线程与其他线程竞争重新获得锁。如果调用 wait()时,没有持有适当的锁,则抛出 IllegalMonitorStateException,它是 RuntimeException 的一个子类,因此,不需要 try-catch 结构。

notify()

public final native void notify() throws IllegalMonitorStateException

该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用 notify()时没有持有适当的锁,也会抛出 IllegalMonitorStateException。

该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个 wait()状态的线程来发出通知,并使它等待获取该对象的对象锁(notify 后,当前线程不会马上释放该对象锁,wait 所在的线程并不能马上获取该对象锁,要等到程序退出 synchronized 代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象notify的线程们。当第一个获得了该对象锁的 wait 线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用 notify 语句,则即便该对象已经空闲,其他 wait 状态等待的线程由于没有得到该对象的通知,会继续阻塞在 wait 状态,直到这个对象发出一个 notify 或 notifyAll。这里需要注意:它们等待的是被 notify 或 notifyAll,而不是锁。这与下面的 notifyAll()方法执行后的情况不同。

notifyAll()

public final native void notifyAll() throws IllegalMonitorStateException

该方法与 notify ()方法的工作方式相同,重要的一点差异是:

notifyAll 使所有原来在该对象上 wait 的线程统统退出 wait 的状态(即全部被唤醒,不再等待 notify 或 notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll 线程退出调用了 notifyAll 的 synchronized 代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

深入理解

如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

有了这些理论基础,后面的notify可能会导致死锁,而notifyAll则不会的例子也就好解释了


出处链接:https://www.zhihu.com/question/37601861/answer/73456344

二者的应用场景在并发情况下还是有所不同的。
当一个线程进入wait之后,就必须等其他线程notify/notifyall,使用notifyall,可以唤醒
所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个。注意,任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized 中的代码,notifyall只是让处于wait的线程重新拥有锁的争夺权,但是只会有一个获得锁并执行。
那么notify和notifyall在效果上又什么实质区别呢?
主要的效果区别是notify用得不好容易导致死锁,例如下面提到的例子。

public final ArrayList<Object> buf = new ArrayList<Object>();
public final static int MAX_SIZE = 100;

public synchronized void put(Object o) throws InterruptedException
{
	while (buf.size() == MAX_SIZE)
	{
		wait(); // called if the buffer is full (try/catch removed for
				// brevity)
	}
	buf.add(o);
	notify(); // called in case there are any getters or putters waiting
}

public synchronized Object get() throws InterruptedException
{

	// Y: this is where C2 tries to acquire the lock (i.e. at the beginning
	// of the method)
	while (buf.size() == 0)
	{
		wait(); // called if the buffer is empty (try/catch removed for
				// brevity)
				// X: this is where C1 tries to re-acquire the lock (see
				// below)
	}
	Object o = buf.remove(0);
	notify(); // called if there are any getters or putters waiting
	return o;

}

当多个线程get 多个线程set的时候,如果只是notify,很有可能产生死锁。 

------------------------------------------------------------------------------------------------

 节选《Java编程思想》之《notify()vs.notifyAll()》:

Using notify() instead of notifyAll() is an optimization.Only one task of the possible many that are waiting on a lock will be awoken with notify(), so you  must be certain that the right task will wake up if you try to use notify().In additon, all tasks must be waiting on the same condition in order for you to use notify(),because if you have tasks that are waiting on different conditions,you don`t konw if the right one will wake up. If you use notify(),only one task must benefit when the condition changes.Finally,these constraints must always be true for all possible subclasses.If any of these rules cannot be met,you must use notifyAll() rather than notify().One of the confusing statements often made in discussions of Java threading is that notifyAll() wakes up "all waiting tasks." Dose this mean that any task that is in a wait(),anywhere in the program, is awoken by any call to notifyAll()?In the following example,the code associated with Task2 shows that this is not true——in fact,only the tasks that are waiting on a particular lock are awoken when notifyAll() is called for that lock:

翻译:

使用notify()而不是notifyAll()是一个优化。只有一个正在等待锁定的任务中的一个任务将被notify()唤醒,所以你必须确定如果你试图找到正确的任务就会被唤醒 使用notify()。另外,所有任务必须在相同条件下等待才能使用notify(),因为如果你有等待不同条件的任务,如果正确的任务将被唤醒,你就不会知道 向上。 如果使用notify(),则只有一个任务必须在条件更改时受益。最后,对于所有可能的子类,这些约束必须始终为true。如果无法满足任何这些规则,则必须使用notifyAll()而不是notify() 在Java线程讨论中经常提出的一个令人困惑的陈述是notifyAll()唤醒“所有等待的任务”。 这意味着任何在程序中任何位置的wait()中的任务都被任何对notifyAll()的调用唤醒?在下面的例子中,与Task2相关的代码表明这不是真的 - 事实上, 当为该锁调用notifyAll()时,只会唤醒正在等待特定锁的任务。

猜你喜欢

转载自blog.csdn.net/wjx_jasin/article/details/82698827