一、等待/通知机制
什么是等待/通知机制?
- 在单线程中,如果要执行的代码需要满足一定条件才能执行,可以放到if语句块里。
- 但在多线程中,可能A线程的执行的条件暂时没有满足,稍后B线程执行到一定程度,使得A线程的执行条件满足,再执行完A线程。可以将A线程先暂停,直到条件满足后再将A线程唤醒。
二、 等待通知机制的实现
Object类中有一个wait()方法,可以使执行的当前代码线程等待,暂停执行,直到接到通知,或者被中断。
注意:
(1)wait()方法只能在同步代码块中由锁对象调用。
(2)调用wait()方法后,当前线程会暂停,并释放锁对象。
Object类的notify()可以唤醒线程,该方法也必须在同步代码块中由锁对象调用(不然会抛异常)。如果有多个等待的线程,notify()方法只能唤醒其中一个。在同步代码块中调用notify()方法,不会立马释放锁对象,需要等当前同步代码块执行完,才会释放,所以我们一般把notify()放在同步代码块的最后。
- 通过等待通知机制,实现2个线程交换打印输出奇数和偶数。
//先写一个多个线程需要共享的同一个对象的类。
public class Num{
int i;
}
//专门打印偶数的线程
public class Even implements Runnable{
Num num;
public Even(Num num) {
this.num = num;
}
@Override
public void run() {
while (num.i < 100){
synchronized (num){
if(num.i % 2 != 0){
try {
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "---->" + num.i);
num.i++;
num.notify();
}
}
}
}
//专门打印奇数的线程
public class Odd implements Runnable{
Num num;
public Odd(Num num) {
this.num = num;
}
@Override
public void run() {
while(num.i < 100){
synchronized (num){
if(num.i % 2 == 0){
try {
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "---->" + num.i);
num.i++;
num.notify();
}
}
}
}
//main方法测试一下
public static void main(String[] args) {
Num num = new Num();
Thread t1 = new Thread(new Odd(num));
Thread t2 = new Thread(new Even(num));
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
分析一下以上代码:
- 我们将奇数线程记作A线程,偶数线程记作B线程
当创建出这两个线程后,启动他们,如果A抢到了时间片,那么由于i的初始值为0,符合A中代码块的if语句,所以执行锁对象的wait方法,A开始等待(即使A第二次又抢到了时间片,还是会继续执行wait方法开始等待)。当B抢到了时间片,由于i不满足if语句,所以i自增,再执行锁对象的notify方法,另外一个共享锁对象的A就会被唤醒(若有多个这样的A线程,究竟唤醒哪个线程是不一定的),开始执行,由于这时的i是1,不满足if语句,所以i自增,并唤醒另一个共享锁对象的B。周而复始,实现了两个线程交替打印奇数和偶数。
三、 interrupt()方法会中断wait()
- 当线程处于wait()等待状态时,调用线程对象的interrupt()方法会中断线程的等待状态。(是通过抛异常的方式中断,InterruptedException异常)
四、notify()和notifyAll()
- notify()一次只能唤醒一个线程,如果有多个等待的线程,只会随机唤醒其中的某一个(这被称为信号丢失),想要所有等待线程都被唤醒,需要调用notifyAll()。