Java中的wait()方法详解
在Java编程语言中,wait()方法是Object类中的一个重要方法。它与线程的协作和线程同步密切相关。本文将详细介绍Java中的wait()方法的用法、原理以及常见的注意事项。
1. wait()方法的概述
Java的Object类中定义了三个与线程同步相关的方法,分别是wait()、notify()和notifyAll()。wait()方法用于将当前线程置入休眠状态,直到其他线程调用对象的notify()或notifyAll()方法唤醒该线程。
声明如下:
public final void wait() throws InterruptedException
2. wait()方法的用法
wait()方法必须在synchronized块或synchronized方法中使用。以下是wait()方法的常见用法:
(1) 暂停线程的执行
我们可以使用wait()方法暂停线程的执行,直到得到特定的通知。在这之前,我们需要先获取对象的锁,以确保线程安全。
public class WaitExample {
public static void main(String[] args) {
final Object lock = new Object();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
System.out.println("Thread 1 is waiting...");
lock.wait();
System.out.println("Thread 1 is notified and resumed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("Thread 2 is running...");
lock.notify();
System.out.println("Thread 2 sent notification to Thread 1.");
}
}
});
thread1.start();
thread2.start();
}
}
上述代码中,我们创建了两个线程:thread1和thread2。其中,thread1在获得lock对象锁之后,调用了lock.wait()方法,使线程进入等待状态。而thread2在获得lock对象锁之后,调用了lock.notify()方法,通知thread1继续执行。以下是代码的输出:
Thread 1 is waiting...
Thread 2 is running...
Thread 2 sent notification to Thread 1.
Thread 1 is notified and resumed.
可以看到,Thread 1在等待状态被Thread 2的通知唤醒后,继续执行。
(2) 超时等待
我们可以在wait()方法中传入一个超时时间,以避免长时间等待而无法被唤醒。
public class WaitExample {
public static void main(String[] args) {
final Object lock = new Object();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
System.out.println("Thread 1 is waiting...");
lock.wait(5000);
System.out.println("Thread 1 is notified and resumed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
System.out.println("Thread 2 is running...");
lock.notify();
System.out.println("Thread 2 sent notification to Thread 1.");
}
}
});
thread1.start();
thread2.start();
}
}
在上述代码中,我们将thread1的等待时间设置为5000毫秒(即5秒)。而thread2的睡眠时间为2000毫秒(即2秒)。由于thread1等待的时间超过了thread2睡眠的时间,所以在thread1被唤醒时,thread2已经结束了执行。以下是代码的输出:
Thread 1 is waiting...
Thread 2 is running...
Thread 2 sent notification to Thread 1.
Thread 1 is notified and resumed.
可以看到,尽管设置了超时时间,但在thread1被唤醒后依然可以继续执行。
3. wait()方法的原理
wait()方法的实现依赖于对象的监视器(monitor)。当调用wait()方法时,当前线程会释放对象的锁,并进入等待状态。只有当其他线程调用该对象的notify()或notifyAll()方法时,才会重新获取对象的锁并继续执行。
当一个线程调用wait()方法后进入等待状态,它会释放锁,并将自己放入等待队列中。当另一个线程调用notify()或notifyAll()方法后,等待队列中的线程会开始竞争获取锁。获得锁的线程将继续执行,而没有获得锁的线程将继续等待。
4. wait()方法的注意事项
在使用wait()方法时,需要注意以下事项:
(1) 必须在同步代码块或同步方法中使用wait()
由于wait()方法依赖于对象的监视器,所以它必须在synchronized块或synchronized方法中使用。否则,会抛出IllegalMonitorStateException异常。
(2) 必须在循环中使用wait()
由于线程可能会出现虚假唤醒(spurious wake-up),即线程在没有被其他线程通知的情况下被唤醒,所以在使用wait()方法时,应该总是在一个循环中使用,以避免在条件不满足的情况下被错误地唤醒。
public class WaitExample {
public static void main(String[] args) {
final Object lock = new Object();
boolean condition = false;
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
while (!condition) {
System.out.println("Thread 1 is waiting...");
lock.wait();
}
System.out.println("Thread 1 is notified and resumed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("Thread 2 is running...");
condition = true;
lock.notify();
System.out.println("Thread 2 sent notification to Thread 1.");
}
}
});
thread1.start();
thread2.start();
}
}
在上述代码中,我们使用了一个boolean类型的变量condition作为条件。在thread1中,我们通过while循环不断检查condition的值,以防止虚假唤醒。以下是代码的输出:
Thread 1 is waiting...
Thread 2 is running...
Thread 2 sent notification to Thread 1.
Thread 1 is notified and resumed.
可以看到,尽管使用了wait()方法,但在满足条件后才被正确地唤醒。
(3) 使用notifyAll()唤醒所有等待线程
在某些情况下,我们可能需要唤醒多个等待中的线程,而不仅仅是一个线程。这时我们可以使用notifyAll()方法来实现。
public class WaitExample {
public static void main(String[] args) {
final Object lock = new Object();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
System.out.println("Thread 1 is waiting...");
lock.wait();
System.out.println("Thread 1 is notified and resumed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("Thread 2 is running...");
lock.notifyAll();
System.out.println("Thread 2 sent notification to all waiting threads.");
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
System.out.println("Thread 3 is waiting...");
lock.wait();
System.out.println("Thread 3 is notified and resumed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread1.start();
thread3.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
在上述代码中,我们创建了三个线程:thread1、thread2和thread3。当thread1和thread3进入等待状态时,我们调用lock.notifyAll()方法来唤醒所有等待中的线程。以下是代码的输出:
Thread 1 is waiting...
Thread 3 is waiting...
Thread 2 is running...
Thread 2 sent notification to all waiting threads.
Thread 3 is notified and resumed.
Thread 1 is notified and resumed.
可以看到,使用notifyAll()方法成功唤醒了所有等待中的线程。
(4) wait()方法可以抛出InterruptedException异常
在使用wait()方法时,需要注意它可能会抛出InterruptedException异常。这是由于线程在等待状态下,可能会被其他线程中断,从而导致wait()方法抛出异常。
为了处理InterruptedException异常,我们可以将其放在try-catch块中进行捕获和处理。通常,我们可以选择在捕获异常后终止线程的执行,或者根据具体需求进行特定的处理。
public class WaitExample {
public static void main(String[] args) {
final Object lock = new Object();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
System.out.println("Thread 1 is waiting...");
lock.wait();
System.out.println("Thread 1 is notified and resumed.");
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt(); // 终止线程的执行
}
}
}
});
thread1.start();
thread1.interrupt(); // 中断等待中的线程
}
}
在上述代码中,我们在主线程中使用thread1.interrupt()方法中断thread1线程的等待。当thread1在等待状态中被中断时,会抛出InterruptedException异常,并终止线程的执行。
(5) wait()方法的使用场景
wait()方法在多线程编程中有着广泛的应用场景。以下是一些常见的使用场景:
- 线程间的协作和同步:wait()方法可以使线程在某个条件满足之前进入等待状态,等待其他线程的通知。这在生产者和消费者模式中得到了广泛的应用。
- 实现线程安全的队列:通过使用wait()和notify()/notifyAll()方法,我们可以实现一个线程安全的队列,确保线程的安全访问。
- 线程间的互斥执行:通过使用wait()方法,我们可以实现多个线程之间的互斥执行,即同一时间只允许一个线程进入某个临界区域。
5. 总结
本文详细介绍了Java中的wait()方法的用法、原理以及注意事项。我们了解到wait()方法的主要作用是暂停线程的执行,并在得到特定通知后继续执行。在使用wait()方法时,需要注意在同步代码块或同步方法中使用,并在循环中使用以避免虚假唤醒。同时,wait()方法可能会抛出InterruptedException异常,需要进行适当处理。
通过合理运用wait()方法,我们可以实现线程之间的协作和同步,保证线程的安全访问,并实现线程间的互斥执行。这使得多线程编程更加灵活和高效。