我们知道线程最核心的一点就是抢占式执行,调度的过程是随机,有时候我们希望可以调配线程的执行顺序。为此wait和notify应运而生。举个例子:wait和notify就类似队友A传球给B,B完成扣篮;B要扣篮就要先wait,等到球传过来;A传球过去,就相当于notify.
以一个线程wait等待,一个线程notify为例
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
//线程1进行wait操作
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("wait之前");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait之后");
}
};
//线程2进行notify操作
Thread t2 = new Thread() {
@Override
public void run() {
object.notify();
}
};
t1.start();
t2.start();
}
}
我们预期会打印“wait之前”和"wait之后",但是却编译报错
原来在wait操作的时候,内部做了三件事
- 释放当前锁
- 进行等待通知
- 满足一定条件的时候(notify),被唤醒,然后重新尝试获取锁
既然要先释放当前锁,那前提肯定是加锁啦。
问:为什么要先释放锁,才进行等待通知?
答:举个例子,A要去ATM取钱,此时A就相当于对ATM进行加锁操作,此时ATM没钱,A就需要离开ATM(释放锁)让工作人员放钱进去(让其他线程加锁),然后工作人员说可以去取钱了(notify唤醒wait),A在重新去取钱(重新获取锁)。
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
//线程1进行wait操作
Thread t1 = new Thread() {
@Override
public void run() {
synchronized (object) {//wati之前先加锁
System.out.println("wait之前");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait之后");
}
}
};
t1.start();
//由于线程的抢占式执行,为了防止线程t2先执行,调用notify方法,而线程t1还没进入wait
//导致后续线程t1一直处于wait,线程阻塞状态
Thread.sleep(500);
//线程2进行notify操作
Thread t2 = new Thread() {
@Override
public void run() {
synchronized (object) {//保证加锁对象和调用wait对象是同一个对象
System.out.println("notify之前");
object.notify();//同时也要保证调用wait的对象和调用notify对象也是同一个对象
System.out.println("notify之后");
}
}
};
t2.start();
}
}
注意点:
- 要保证wait和notify加锁对象相同
- 要保证调用wait和notify的对象,是同一个对象
上述代码的流程图
除了notify,还有notifyAll,多个线程都在wait时,notify是随机唤醒一个线程,而notifyAll则是全部唤醒,这些线程要全部重新竞争锁