Java 中线程基本知识及操作(二):线程的协作 wait/notify、join/yield、suspend/resume等机制
欢迎关注我的公众号:观知小阁。包含各种类的文章,内容更丰富,更新及时且不迷路。
了解与理解多线程协作的原理与技巧
在Java多线程编程中,仅仅理解如何创建和启动线程是远远不够的。真正的挑战在于如何让多个线程高效协作,共同完成复杂任务。
今天,我们来了解Java中线程协作的关键机制,包括wait/notify、挂起和继续执行、结束和谦让等操作。
1. 等待(wait)和通知(notify)机制
在多线程环境中,线程间的协作至关重要。JDK提供了wait()和notify()方法来实现这种协作,这两个方法定义在Object类中,意味着任何Java对象都可以作为线程协作的工具。
1.1 基本工作原理
当线程调用一个对象的wait()方法后,它会进入等待状态,直到其他线程调用该对象的notify()或notifyAll()方法。这个过程涉及两个关键队列:等待队列和锁竞争队列。
// 简单的wait/notify示例
public class WaitNotifyExample {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread waitingThread = new Thread(() -> {
synchronized(lock) {
try {
System.out.println("线程进入等待状态...");
lock.wait();
System.out.println("线程被唤醒并继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waitingThread.start();
Thread.sleep(1000); // 主线程休眠1秒
synchronized(lock) {
lock.notify(); // 唤醒等待的线程
}
}
}
1.2 wait()和notify()的执行条件
关键点在于:这两个方法都必须在同步环境(synchronized方法或代码块)中调用。线程在调用wait()方法前必须获得对象锁,而wait()方法执行后会释放这个锁,使其他线程有机会获得锁并执行通知操作。
与Thread.sleep()不同,wait()方法会释放目标对象的锁,而sleep()则不会释放任何锁资源。
1.3 生产者 - 消费者模式实战
wait/notify机制最典型的应用就是 生产者 - 消费者模式:
public class ProducerConsumerExample {
private static final List<String> list = new ArrayList<>();
private static final Object lock = new Object();
// 生产者线程
static class Producer extends Thread {
public void run() {
while (true) {
synchronized(lock) {
// 如果列表中有数据,就等待消费者消费
if (list.size() > 0) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生产数据
list.add("数据:" + System.currentTimeMillis());
System.out.println("生产数据: " + list.get(0));
lock.notify(); // 通知消费者
}
}
}
}
// 消费者线程
static class Consumer extends Thread {
public void run() {
while (true) {
synchronized(lock) {
// 如果列表中没有数据,就等待生产者生产
if (list.size() == 0) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费数据
System.out.println("消费数据: " + list.remove(0));
lock.notify(); // 通知生产者
}
}
}
}
}
在这个模式中,当条件不满足时,线程会主动等待,并在条件满足时被通知继续执行,从而高效地协调生产者和消费者的工作节奏。
2. 挂起(suspend)和继续执行(resume)的隐患
Thread类曾提供了suspend()和resume()方法用于线程挂起和恢复,但这些方法已被标记为过时,不推荐使用。
2.1 为什么被弃用?
主要问题在于suspend()方法在挂起线程时不会释放它持有的锁。如果在一个同步代码块中挂起线程,其他需要访问该同步资源的线程会被阻塞,极易导致死锁发生。
// 有问题的suspend/resume示例
public class SuspendResumeExample {
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized(lock) {
System.out.println("t1获得锁");
Thread.currentThread().suspend(); // 挂起但不释放锁
System.out.println("t1继续执行");
}
});
t1.start();
Thread.sleep(100); // 确保t1先执行
Thread t2 = new Thread(() -> {
synchronized(lock) { // 这个调用将一直被阻塞,因为t1持有了锁但没有释放
System.out.println("t2获得锁");
}
});
t2.start();
t1.resume(); // 尝试恢复t1
}
}
在上面的例子中,t2线程会一直阻塞,因为t1在挂起时没有释放lock的锁,即使后续调用了resume(),也可能由于时机问题导致系统无法正常继续执行。
2.2 安全的替代方案
相比使用suspend()和resume(),更安全的做法是使用wait/notify机制或条件变量:
// 使用wait/notify替代suspend/resume
public class SafeSuspendResume {
private static final Object lock = new Object();
private static boolean suspended = false;
public static void main(String[] args) {
Thread worker = new Thread(() -> {
while (true) {
synchronized(lock) {
while (suspended) {
try {
lock.wait(); // 等待直到被通知
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
// 执行实际工作
System.out.println("工作线程运行中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
worker.start();
// 暂停工作线程
synchronized(lock) {
suspended = true;
}
// 恢复工作线程
synchronized(lock) {
suspended = false;
lock.notifyAll();
}
}
}
这种方式避免了长期持有锁的问题,更加安全可靠。
3. 等待线程结束(join)和谦让(yield)
3.1 join:让线程有序结束
当一个线程需要等待另一个线程执行完毕时,可以使用join()方法。这在线程间有依赖关系的场景中非常有用。
public class JoinExample {
public static void main(String[] args) throws InterruptedException {
Thread worker1 = new Thread(() -> {
System.out.println("工作者1开始工作");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("工作者1完成工作");
});
Thread worker2 = new Thread(() -> {
System.out.println("工作者2开始工作");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("工作者2完成工作");
});
worker1.start();
worker2.start();
// 主线程等待worker1和worker2完成
worker1.join();
worker2.join();
System.out.println("所有工作者已完成任务,主线程继续执行");
}
}
join()方法会使当前线程进入等待状态,直到目标线程执行完毕。还可以使用join(long millis)设置最大等待时间,避免无限期等待。
3.2 yield:线程的谦让行为
Thread.yield()是一个静态方法,它提示调度器当前线程愿意让出CPU使用权,但这只是一个提示,不保证调度器一定会采纳。
public class YieldExample {
static class Worker extends Thread {
private final String name;
public Worker(String name) {
this.name = name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + " 执行步骤 " + i);
// 在特定条件下让出CPU
if (i == 2) {
System.out.println(name + " 让出CPU");
Thread.yield();
}
}
}
}
public static void main(String[] args) {
new Worker("线程A").start();
new Worker("线程B").start();
}
}
yield()方法适用于优先级相等或更高的线程间协调,它可以让出CPU资源,给其他线程执行机会,但不改变锁的状态。
4. 线程状态与协作要点
理解Java线程的各种状态对正确使用协作机制至关重要:
-
NEW:线程创建但尚未启动
-
RUNNABLE:线程正在JVM中执行
-
BLOCKED:线程被阻塞等待监视器锁
-
WAITING:线程无限期等待另一个线程的操作
-
TIMED_WAITING:线程有限期等待
-
TERMINATED:线程已退出执行

wait()方法会使线程进入WAITING或TIMED_WAITING状态,而notify()和notifyAll()则可以将线程从等待状态移回锁竞争队列。
5. 实战建议与常见陷阱
5.1 正确使用wait/notify的模式
为避免丢失通知或无限等待问题,推荐使用以下模式:
public class CorrectWaitNotify {
private final Object lock = new Object();
private boolean condition = false;
public void awaitCondition() throws InterruptedException {
synchronized(lock) {
// 始终在循环中检查条件
while (!condition) {
lock.wait();
}
// 条件满足后执行相应操作
System.out.println("条件满足,继续执行");
}
}
public void satisfyCondition() {
synchronized(lock) {
condition = true;
lock.notifyAll(); // 优先使用notifyAll而非notify
}
}
}
关键点:
-
总是在循环中检查条件,避免虚假唤醒
-
优先使用notifyAll(),除非确实只需要唤醒一个线程且性能考虑极为重要
-
确保在条件改变后发出通知
6. 总结
Java线程协作机制是一把双刃剑,正确使用可以大幅提升程序性能和解耦设计,而误用则会导致性能问题甚至死锁。以下是关键要点总结:
|
机制 |
优点 |
缺点 |
适用场景 |
|---|---|---|---|
|
wait/notify |
释放锁资源,高效协作 |
需在同步环境中使用 |
生产者-消费者模式 |
|
join |
简单直观保证执行顺序 |
会阻塞当前线程 |
线程间依赖关系强 |
|
yield |
提示性谦让,避免饥饿 |
效果不确定 |
平衡同等优先级线程 |
记住核心原则:多线程编程的首要目标是正确性,其次才是性能。在复杂场景中,考虑使用java.util.concurrent包中的高级工具类,它们封装了这些底层机制,提供了更安全、高效的并发解决方案。
更多推荐


所有评论(0)