在 Java 中,wait
和 notify
方法是 Object
类提供的用于线程间通信和同步的关键方法。它们必须在 同步块 或 同步方法 中调用,原因如下:
线程安全
同步块或同步方法确保同一时刻只有一个线程可以访问共享资源或代码块。当一个线程调用 wait
方法时,它会释放锁并进入 等待 状态,直到被另一个线程调用 notify
或 notifyAll
方法唤醒。
如果 wait
和 notify
方法不在同步块或同步方法中调用,则可能导致 竞争条件。例如,如果一个线程调用 wait
方法后,另一个线程在没有获得锁的情况下修改了共享资源,则第一个线程在被唤醒后可能会使用过时的或不一致的数据。
死锁避免
死锁是指两个或多个线程互相等待对方释放锁的情况,导致所有线程都被阻塞。如果 wait
和 notify
方法不在同步块或同步方法中调用,则可能导致死锁。
例如,考虑以下代码:
public class DeadlockExample {
private Object lock = new Object();
public void method1() {
synchronized (lock) {
// 执行任务 1
}
}
public void method2() {
// 没有同步块
lock.wait();
}
}
在这个示例中,method1()
是一个同步方法,而 method2()
没有同步块。如果线程 1 调用 method1()
并获得锁,然后线程 2 调用 method2()
,则线程 2 将一直等待,因为线程 1 还没有释放锁。同时,线程 1 也无法继续执行,因为它正在等待线程 2 释放锁。这将导致死锁。
最佳实践
为了避免竞争条件和死锁,始终在同步块或同步方法中调用 wait
和 notify
方法。这将确保在同一时间只有一个线程可以访问共享资源或执行代码块,从而保证线程安全和避免死锁。
示例
以下代码演示了如何正确地在同步块中调用 wait
和 notify
方法:
public class ThreadCommunication {
private Object lock = new Object();
private boolean ready = false;
public void producer() {
synchronized (lock) {
while (!ready) {
try {
lock.wait();
} catch (InterruptedException e) {
// 处理中断
}
}
// 生产数据
// 唤醒消费者线程
lock.notify();
}
}
public void consumer() {
synchronized (lock) {
while (!ready) {
try {
lock.wait();
} catch (InterruptedException e) {
// 处理中断
}
}
// 消费数据
}
}
}
在这个示例中,生产者线程在 producer()
方法中使用一个同步块来确保在生产数据之前不会被唤醒。消费者线程在 consumer()
方法中也使用一个同步块来确保在数据准备好之前不会被唤醒。这样可以防止竞争条件和死锁。
总结
从同步块或同步方法调用 wait
和 notify
方法对于确保线程安全和避免死锁至关重要。通过遵循此最佳实践,可以编写健壮且可行的多线程应用程序。