线程的等待与通知
我们知道. 线程在操作系统上是随机调度执行的. 那么如果我们想要控制线程之间某个逻辑执行的先后顺序, 该怎么办呢? --> wait和notify能够为我们解决这个问题.
在Java中, wait()方法是Object类提供的一个方法, 当一个线程调用了某个对象的wait()方法时, 该线程会暂停执行,并等待直到另一个线程在该对象上调用notify()或notifyAll()方法, 这通常用于实现线程间的通信.
我们可以让后执行的线程使用wait进行等待, 当先执行的线程执行完某些逻辑之后, 通过notify唤醒对应的wait, 相当于"通知"了后执行的线程, 让后执行的线程继续执行.
[注]: wait中一定会执行"解锁"的操作, 所以在调用wait()方法时, 一定要将其放到synchronized代码块中 (先加上锁才能谈解锁), 否则程序就会报错. 另外, notify()的执行也需要持有锁资源 (放到synchronized里面), 否则程序也会报错.
一. 单个线程的等待和唤醒
1. 例子
import java.util.Scanner;
public class Demo21 {
private static Object locker = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (locker) {
System.out.println("t1 wait 之前");
try {
locker.wait(); // 释放锁并进入阻塞状态.
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t1 wait 之后");
}
});
Thread t2 = new Thread(() -> {
System.out.println("t2 notify 之前");
Scanner scanner = new Scanner(System.in);
scanner.next(); // 此处用户输入啥都行, 主要是通过这个 next, 构造一个"阻塞"状态.
synchronized (locker) { //线程t1执行wait就会将locker释放, 所以这里线程t2可以顺利拿到locker
locker.notify();
}
System.out.println("t2 notify 之后");
});
t1.start();
t2.start();
}
}
线程t1是后执行的线程, 执行wait()进入等待状态. 线程t2是先执行的线程, 执行完它的任务之后会执行notify(), 通知线程t1结束等待. 我们看运行结果:
我们可以看到, 当线程t2执行notify通知t1结束线程后, t1立刻结束等待, 执行后面的语句"t1 wait 之后".
[注意]: wait执行之后, 是要执行3部分内容的: (1) 释放锁资源. (2) 进入等待状态. (3) 收到notify通知之后, 唤醒等待状态, 继续往下执行. 由于这里执行wait会释放锁资源, 所以无法保证wait中执行的指令是"原子的", 所以就有可能出现一种情况: wait释放锁资源之后, 话没有来得及开始等待, 另一个线程就执行notify了. 由于当前线程还没有进入真正的等待状态. 所以它无法接收到notify的通知. 所以也就无法结束等待, 会持续阻塞下去.
wait还有带时间参数的版本: wait(long timeout), 这个版本指定了超时时间, 如果已经达到了最大等待时间还没有接收到notify通知的话, 那么wait会自己结束等待状态, 继续往下执行.
2. wait(long timeout)和sleep(long timeout)的区别
(1) 使用wait的目的是为了唤醒, 而sleep就是固定时间的阻塞, 不涉及唤醒.
(2) wait必须要搭配synchronized使用, 而sleep与加不加锁没有任何关系.
二. 多个线程的等待和唤醒
1. 例子
import java.util.Scanner;
public class Demo22 {
private static Object locker = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (locker) {
System.out.println("t1 wait 之前");
try {
locker.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t1 wait 之后");
}
});
Thread t2 = new Thread(() -> {
synchronized (locker) {
System.out.println("t2 wait 之前");
try {
locker.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t2 wait 之后");
}
});
Thread t3 = new Thread(() -> {
synchronized (locker) {
System.out.println("t3 wait 之前");
try {
locker.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t3 wait 之后");
}
});
Thread t4 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
scanner.next(); //阻塞状态
synchronized (locker) {
locker.notify();
locker.notify();
locker.notify();
}
System.out.println("t4 notify 之后");
});
t1.start();
t2.start();
t3.start();
t4.start();
}
}
上述代码三个线程执行三个wait(), t4线程执行三个notify来唤醒这三个线程.
有三个wait应该notify三次, 但是notify多次也可以 (没有任何副作用).
2. notifyAll()
上面是三个线程等待, 唤醒三次, 那如果有很多个线程等待呢? --> java中提供了一个notifyAll()方法, 用于一次性唤醒全部线程.
3. 规定执行顺序
public class Demo23 {
private static Object locker1 = new Object();
private static Object locker2 = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.print("A");
try {
Thread.sleep(100); //这里休眠100ms保证是t2先拿到locker1进入wait状态的.
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (locker1) {
locker1.notify();
}
});
Thread t2 = new Thread(() -> {
synchronized (locker1) {
try {
locker1.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.print("B");
synchronized (locker2) {
locker2.notify();
}
}
});
Thread t3 = new Thread(() -> {
synchronized (locker2) {
try {
locker2.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("C");
}
});
t1.start();
t2.start();
t3.start();
}
}
如上述代码: 创建了两个锁对象locker1和locker2. 其中t2线程拿到locker1并释放进入等待, t3线程拿到locker2并释放进入等待. t1线程打印完"A"之后, 拿到locker1通知线程t2结束等待. t2结束等待之后, 打印"B", 然后拿到locker2, 通知线程t3结束等待. 然后t3打印"C". 所以这个代码的执行结果只能是ABC. (但是如果不加wait和notify的话, 实际的执行情况并不能确定).
三. 小结
(1) wait和notify的主要作用是对线程的执行顺序做出干预
(2) wait和notify都是Object类提供的方法.
(2) wait和notify都需要搭配synchronized来使用.
(3) wait方法提供了无参版本, 也提供了带超时时间参数的版本.
(4) 如果有多个线程都处于wait状态, 但是只有一个notify, 那么这个notify会随机唤醒其中一个wait状态的线程.
(5) notifyAll能够唤醒所有正在wait的线程.
(6) 如果notify调用次数超过了wait的次数, 会将全部wait都唤醒. (没有任何副作用).
好了, 本篇文章就介绍到这里啦, 大家如果有疑问欢迎评论, 如果喜欢小编的文章, 记得点赞收藏~~