我们先看看代码片段,有点类似生产者-消费者模型中的生产者的逻辑代码。
public synchronized void push(Object obj){
while( condition ){
try{
this.wait(); //等待,直到有数据出栈
}catch(InterruptedException e){
}
}
//do something
stack.push(obj);
this.notify(); //通知其它线程把数据出栈
}
注意第4行,代码执行到这里会发生什么状态?
push方法的签名中有synchronized修饰,所以此方法具有排他性,锁(资源)为自身实例对象,也就是说同时同一个对象的这个方法只能被一个线程所执行,那么当执行到第4行时会发生什么呢? XX一紧,线程在释放排他锁资源之前,自己把自己给阻塞了? 这样岂不是死锁了?
翻翻JDK1.6手册,便恍然大悟:
wait方法共有3个重载函数:
- public final void wait(long timeout) throws InterruptedException
- public final void wait(long timeout, int nanos) throws InterruptedException
- public final void wait() throws InterruptedException
在其他线程调用此对象的
notify()
方法或
notifyAll()
方法,或者超过指定的时间量前,导致当前线程等待。此方法导致当前线程(称之为
T)将其自身放置在对象的等待集中,然后放弃此对象上的所有同步要求。出于线程调度目的,在发生以下四种情况之一前,线程
T 被禁用,且处于休眠状态:
- 其他某个线程调用此对象的 notify 方法,并且线程 T 碰巧被任选为被唤醒的线程。
- 其他某个线程调用此对象的 notifyAll 方法。
- 其他某个线程中断线程 T。
- 大约已经到达指定的实际时间。但是,如果 timeout 为零,则不考虑实际时间,在获得通知前该线程将一直等待。
然后,从对象的等待集中删除线程
T,并重新进行线程调度。然后,该线程以常规方式与其他线程竞争,以获得在该对象上同步的权利;一旦获得对该对象的控制权,该对象上的所有其同步声明都将被恢复到以前的状态,这就是调用
wait 方法时的情况。然后,线程
T 从
wait 方法的调用中返回。所以,从
wait 方法返回时,该对象和线程
T 的同步状态与调用
wait 方法时的情况完全相同。
简而言之:
- wait方法的作用释放已持有的锁,并进入休眠状态;当被唤醒或是等待超时,当前的线程又会重新加入到锁等待队列中,一旦获取了对该对象的控制权,则会从原来的地方继续执行;如果线程被中断,则会抛出InterruptedException异常。
- notify方法唤醒等待队列中的第一个线程并把它移入锁申请队列中
- notifyAll方法唤醒等待队列中的所有的线程并把它们移入锁申请队列中
- wait,notify,notifyAll必须在已经持有锁的情况下执行,所以它们只能出现在synchronized作用范围内,也就是出现在用synchronized修饰的方法或代码块中.
- 在更多线程的情况下(e.g. 拿消费者/生产者模型来讲,可能存在多个消费者和多个生产者),线程被唤醒后依然需要对条件进行测试,如果不满足该条件,则继续等待。换句话说,等待应总是发生在循环中。如上述代码示例(2-7循环体)
附上完整的示例代码:
public class ThreadDemo {
private int x = 0;
public synchronized void add() {
while (x >= 2) {
try {
this.wait();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
x++;
System.out.println("add:" + x);
this.notifyAll();
}
public synchronized void sub() {
int y = 1;
while (x < 2) {
System.out.println("sub:while repeat = " + y);
y++;
try {
this.wait(); //this.wait(1);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
x--;
System.out.println("sub:" + x);
this.notifyAll();
}
public static void main(String[] args) throws Throwable {
final ThreadDemo tt = new ThreadDemo();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
tt.add();
try {
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
tt.sub();
try {
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
}
}
}
}).start();
}
}
在代码第25行处,可以试着设置下等待超时时间,然后看看测试结果。