Java 多线程编程之 Object.wait 方法(工作原理、高级特性、notify 方法与 notifyAll 方法)
一、wait 方法
1、基本介绍
-
wait 方法是 Java 中每个对象都拥有的方法,它继承自 Object 类
-
wait 方法使当前线程进入等待状态,直到其他线程调用该对象的 notify 方法或 notifyAll 方法
-
wait 方法必须在同步代码块中使用,否则抛出 InterruptedException 异常
public final void wait() throws InterruptedException
public final native void wait(long timeoutMillis) throws InterruptedException;
public final void wait(long timeoutMillis, int nanos) throws InterruptedException
2、基本使用
- 不带超时的 wait 方法
Object o = new Object();
new Thread(() -> {
synchronized (o) {
try {
System.out.println("t1 进入等待状态");
o.wait();
System.out.println("t1 被唤醒后继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
synchronized (o) {
try {
Thread.sleep(2000);
System.out.println("t2 唤醒 t1");
o.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
# 输出结果
t1 进入等待状态
t2 唤醒 t1
t1 被唤醒后继续执行
- 带超时的 wait 方法
Object o = new Object();
new Thread(() -> {
synchronized (o) {
try {
System.out.println("t1 进入等待状态");
o.wait(2000);
System.out.println("t1 被唤醒后继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
# 输出结果
t1 进入等待状态
t1 被唤醒后继续执行
- 必须在同步代码块中使用 wait 方法
Object o = new Object();
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
# 输出结果
Exception in thread "main" java.lang.IllegalMonitorStateException: current thread is not owner
3、wait 方法对比 sleep 方法
特性 | wait 方法 | sleep 方法 |
---|---|---|
所属类 | Object 类 | Thread 类 |
释放锁 | 会释放锁 | 不会释放锁 |
唤醒方式 | notify / notifyAll / 超时 / 中断 | 超时 / 中断 |
使用场景 | 线程间协作 | 暂停 |
二、wait 方法工作原理
1、wait 方法与对象锁的关系
(1)基本介绍
-
调用 wait 方法前:必须持有对象锁
-
调用 wait 方法后:立即释放对象锁
-
被唤醒后:需要重新竞争对象锁,再继续执行
(2)演示
- 被唤醒后,还是需要重新竞争对象锁,再继续执行
Object o = new Object();
new Thread(() -> {
synchronized (o) {
try {
System.out.println("t1 进入等待状态");
o.wait();
System.out.println("t1 被唤醒后继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
synchronized (o) {
try {
Thread.sleep(2000);
System.out.println("t2 唤醒 t1");
o.notify();
Thread.sleep(2000);
System.out.println("t2 执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
# 输出结果
t1 进入等待状态
t2 唤醒 t1
t2 执行完毕
t1 被唤醒后继续执行
- 超时后,还是需要重新竞争对象锁,再继续执行
Object o = new Object();
new Thread(() -> {
synchronized (o) {
try {
System.out.println("t1 进入等待状态");
o.wait(2000);
System.out.println("t1 被唤醒后继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
synchronized (o) {
try {
Thread.sleep(4000);
System.out.println("t2 执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
# 输出结果
t1 进入等待状态
t2 执行完毕
t1 被唤醒后继续执行
2、对象监视器模型
- 每个 Java 对象都有一个关联的监视器(monitor),包含
-
Owner Thread
:当前持有对象锁的线程 -
Entry Set
:等待获取对象锁的线程集合 -
Wait Set
:调用了 wait 方法的线程集合
3、线程状态转换
- 当调用 wait 方法方法时,线程状态会发生一系列变化
-
线程状态转换
RUNNABLE -> WAITING / TIMED_WAITING
-
释放对象锁
-
进入该对象锁的等待集合(
Wait Set
) -
当调用 notify 方法后,线程状态转换
WAITING -> BLOCKED
(等待获取对象锁) -
获取对象锁后,线程状态转换
BLOCKED -> RUNNABLE
三、wait 方法高级特性
1、虚假唤醒
-
虚假唤醒是指线程在没有收到 notify / notifyAll 的情况下从 wait 返回,虚假唤醒可能由以下原因引起
-
某些操作系统的线程调度机制可能导致 wait 提前返回,即使没有收到 notify / notifyAll
-
JVM 实现可能在某些情况下允许虚假唤醒
- 防御虚假唤醒的正确模式:wait 放在 while 语句中检查条件,而不是 if 语句,这样可以确保即使发生虚假唤醒,线程也会重新检查条件并继续等待
synchronized (【锁对象】) {
while (【条件】) {
【锁对象】.wait();
}
// 处理逻辑
}
2、中断处理
(1)基本介绍
- wait 会响应中断,抛出 InterruptedException 异常,中断处理最佳策略是
-
捕获 InterruptedException 异常
-
恢复中断状态,即调用 interrupt 方法
(2)演示
Object o = new Object();
Thread t1 = new Thread(() -> {
synchronized (o) {
try {
System.out.println("t1 进入等待状态");
o.wait();
System.out.println("t1 被唤醒后继续执行");
} catch (InterruptedException e) {
System.out.println("t1 被中断");
System.out.println("t1 中断状态:" + Thread.currentThread().isInterrupted());
Thread.currentThread().interrupt();
System.out.println("t1 中断状态:" + Thread.currentThread().isInterrupted());
}
}
});
Thread t2 = new Thread(() -> {
synchronized (o) {
try {
Thread.sleep(2000);
System.out.println("t2 中断 t1");
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
# 输出结果
t1 进入等待状态
t2 中断 t1
t1 被中断
t1 中断状态:false
t1 中断状态:true
四、notify 方法与 notifyAll 方法
1、基本介绍
- notify 方法随机唤醒等待集合(
Wait Set
)中的 1 个线程
public final native void notify()
- notifyAll 方法唤醒等待集合(
Wait Set
)中的所有线程
public final native void notifyAll()
- notify 方法与 notifyAll 方法必须在同步代码块中使用,否则抛出 InterruptedException 异常
2、演示
- notify 方法
Object o = new Object();
new Thread(() -> {
synchronized (o) {
try {
System.out.println("t1 进入等待状态");
o.wait();
System.out.println("t1 被唤醒后继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
synchronized (o) {
try {
System.out.println("t2 进入等待状态");
o.wait();
System.out.println("t2 被唤醒后继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
synchronized (o) {
try {
Thread.sleep(2000);
System.out.println("t3 随机唤醒 1 个线程");
o.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
# 输出结果
t1 进入等待状态
t2 进入等待状态
t3 随机唤醒 1 个线程
t1 被唤醒后继续执行
- notifyAll 方法
Object o = new Object();
new Thread(() -> {
synchronized (o) {
try {
System.out.println("t1 进入等待状态");
o.wait();
System.out.println("t1 被唤醒后继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
synchronized (o) {
try {
System.out.println("t2 进入等待状态");
o.wait();
System.out.println("t2 被唤醒后继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
synchronized (o) {
try {
Thread.sleep(2000);
System.out.println("t3 唤醒全部线程");
o.notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
# 输出结果
t1 进入等待状态
t2 进入等待状态
t3 唤醒全部线程
t1 被唤醒后继续执行
t2 被唤醒后继续执行
- notify 方法与 notifyAll 方法必须在同步代码块中使用
Object o = new Object();
new Thread(() -> {
synchronized (o) {
try {
System.out.println("t1 进入等待状态");
o.wait();
System.out.println("t1 被唤醒后继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
o.notify();
# 输出结果
t1 进入等待状态
Exception in thread "main" java.lang.IllegalMonitorStateException: current thread is not owner
Object o = new Object();
new Thread(() -> {
synchronized (o) {
try {
System.out.println("t1 进入等待状态");
o.wait();
System.out.println("t1 被唤醒后继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
o.notifyAll();
# 输出结果
t1 进入等待状态
Exception in thread "main" java.lang.IllegalMonitorStateException: current thread is not owner