1.wait()方法和notify()方法
wait()方法和notify()方法是用于线程间的通信和同步的方法。由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知。但是有时候我们希望合理的协调多个线程之间的执行顺序。这时候就需要用到 Object 中的 wait()方法和notify()方法了。
1.1 wait() 方法
如果一个线程调用了wait()方法,那么它会经历以下几个阶段:
- 线程进入等待状态:调用wait()方法会导致当前线程进入等待状态,暂时停止执行。
- 释放对象锁:在调用wait()方法之后,线程会释放它所持有的对象锁。这样,其他线程就有机会获取该对象锁,并执行相应的操作。
- 等待其他线程的通知:一旦线程进入等待状态,它会等待其他线程调用相同对象上的notify()方法或notifyAll()方法来通知它恢复执行。线程会保持等待状态,直到被通知。
补充:可选的等待时间:wait()方法还可以传入一个可选的等待时间参数,用于限制线程等待的时间长度。如果超过指定的等待时间,线程会自动被唤醒。
我们来看看下面的案例:
java复制代码public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread t1 = new Thread(()->{
System.out.println("wait()方法之前");
try {
//要处理 wait() 的异常
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("wait()方法之后");
});
t1.start();
}
}
结果:
结果抛出异常了,为什么呢?原因是:wait()方法必须要和synchronized搭配使用!既然wait()是暂停执行并放弃锁,你连锁都没有何谈放弃呢?
java复制代码public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread t1 = new Thread(()->{
//wait() 必须要和 synchronized 搭配使用
synchronized (object){
System.out.println("wait()方法之前");
try {
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("wait()方法之后");
}
});
t1.start();
}
}
结果:
这时 t1 线程已经阻塞,等待着有缘人使用notify()把它唤醒。如果它不想等待有缘人,这里也可以使用定时的wait(timeout)方法。
java复制代码public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread t1 = new Thread(()->{
synchronized (object){
System.out.println("wait()方法之前");
try {
//1000毫秒后唤醒
object.wait(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("wait()方法之后");
}
});
t1.start();
}
}
结果:
wait 结束等待的条件:
- 其他线程调用该对象的notify方法。
- wait 等待时间超时,wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间。
- 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常。
1.2 notify() 方法
只有wait()可不行呀,不然一直阻塞下去不是办法。所以我们需要使用notify()方法。
对于notify()方法:
- 唤醒等待的线程:调用notify()方法会唤醒在相同对象上(锁)等待的一个线程。如果有多个线程在等待,那么具体唤醒哪个线程是不确定的,取决于操作系统的调度。
- 通知等待线程可以继续执行:一旦线程被唤醒,它会从wait()方法的阻塞中恢复过来,并且可以继续执行后续的操作。
- 不释放对象锁:与wait()方法不同,notify()方法并不会释放对象的锁。唤醒的线程需要等待调用notify()方法的线程释放对象锁后才能继续执行。
java复制代码public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread t1 = new Thread(()->{
System.out.println("t1: wait()方法之前");
try {
synchronized (object){
object.wait();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t1: wait()方法之后");
});
Thread t2 = new Thread(()->{
System.out.println("t2: notify()方法之前");
//notify() 也要与 synchronized 配合使用
synchronized (object){
object.notify();
}
System.out.println("t2: notify()方法之后");
});
t1.start();
//这里的 sleep 是确保先让 t1 阻塞
Thread.sleep(500);
t2.start();
}
}
1.3 notifyAll() 方法
对于notify()方法,如果有多个线程在等待,那么具体唤醒哪个线程是不确定的 。而 notifyAll()方法是唤醒所有等待的线程(同一个锁),然后让这些线程继续争夺锁。
案例:有三个线程,分别只能打印A、B、C。现在要求控制这三个线程分别进行打印,顺序必须为 A、B、C。
java复制代码class Demo4{
//记录当前该打印的字母
static char state = 'A';
public static void main(String[] args) {
Object lock = new Object();
Thread t1 = new Thread(()->{
synchronized (lock){
while(state != 'A'){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print("A");
//A 已经打印了,该打印 B
state = 'B';
//这里需要唤醒所有的阻塞线程,然后它们再判断自己该不该打印
lock.notifyAll();
}
});
Thread t2 = new Thread(()->{
synchronized (lock){
while(state != 'B'){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print("B");
//B 已经打印了,该打印 C
state = 'C';
//这里需要唤醒所有的阻塞线程,然后它们再判断自己该不该打印
lock.notifyAll();
}
});
Thread t3 = new Thread(()->{
synchronized (lock){
while(state != 'C'){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print("C");
}
});
t1.start();
t2.start();
t3.start();
}
}
结果:
1.4 wait() 和 sleep() 的对比
相似之处:
- wait和sleep都可以让当前线程进入非可运行状态。
- wait和sleep都可以指定暂停的时间,单位是毫秒。
- wait和sleep都可以被其他线程用interrupt()中断,抛出InterruptedException异常。
区别:
- wait是Object类的实例方法,而sleep是Thread类的静态方法。
- wait必须在同步块或同步方法中调用,而sleep可以在任何地方调用。
- wait 会释放当前对象的锁,让其他线程可以获取该对象的锁,而sleep不会释放任何锁。
- 在中途唤醒的时候,wait可以不用抛出异常(调用notify或notifyAll);而sleep会抛出异常(调用interrupt())。
- wait通常用于线程间的通信,而sleep通常用于控制线程的执行时间。