论wait跟notify的正确使用方法-二进制信号量的实现
wait和notify可以说是很神奇了,它们主要用于线程之间的协作,但他们却是Object类的方法。首先看看这几个方法的说明。
wait:Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
notify: Wakes up a single thread that is waiting on this object's monitor.
notifyAll: Wakes up all threads that are waiting on this object's monitor.
从里面可以看到一个很重要的概念,就是monitor,java中每一个对象都有一个monitor,而且每个monitor都有一个等待队列,队列中放的是对该对象执行了wait的线程,对该对象执行notify(notifyall)方法,可以唤醒等待队列中的随机一个线程(全部线程)。
wait会让当前线程暂时放弃该对象的monitor,并且排队等候(排队是没有顺序的),notify则是拿到monitor用完的线程告诉任意一个等着的线程,“我用完了,你拿回去用吧!”值得注意的是notifyAll方法,它通知了所有等待的线程,但是还是只有一个线程能拿到monitor,剩下的线程还得继续乖乖地等着。
由于wait和notify方法都会让当前线程放弃已经获得的monitor,所以每个线程只有获得了该对象的monitor,才能对其进行wait或者notify操作,在java中获得某对象的monitor的方式,就是synchronized(Object){ /*********/ }同步块,另外synchronized方法相当于synchronized(this){ /*********/ }。
所以典型的使用方法是:
synchronized(object){ object.wait(); }
还有
synchronized(object){ object.notify(); }
另外wait方法可能会抛出InterruptedException,需要进行处理。
这篇文章的标题是wait/notify的正确使用方法,那么它的正确使用方法是什么呢?那就是不使用它!开个玩笑,我的意思是我们可以把wait和notify包装成我们喜闻乐见的东西,这样代码的可读性更好,程序也会更健壮,举个例子,我们可以用它来实现信号量Semaphore。下面是一个二进制信号量的实现。
package com.cici.lock; /** * @author 尹定宇 * @Email 768166775@qq.com * @version 2013-7-14 下午6:21:50 * @info 二进制信号量 */ public class BinarySemaphore { private boolean isAvailable = false; public BinarySemaphore(){} public BinarySemaphore(boolean Available){ isAvailable = Available; } public synchronized void semTake() throws InterruptedException{ while(!isAvailable){ wait(); } this.isAvailable = false; } public synchronized void semGive(){ notify(); isAvailable = true; } public boolean equals(Object object){ return this==object; } }
与直接使用wait/notify相比,信号量有诸多好处:
1.与操作系统PV操作相似,更容易理解
2.代码中不再需要出现synchronized同步块,更加美观易读
3.完美解决”假唤醒“的问题,将wait放在while循环中,若唤醒条件不具备,继续wait
4.设想一种特定的情景,我们需要一个线程先wait,然后另一个线程notify它,但是不幸地是另一个线程先notify了,所以第一个线程就永远wait了;使用信号量notify会记录在标志中,如果标志可用,就不用wait,该问题完美解决。