深入理解Java中的等待机制(How to Wait in Java)
简介
在Java多线程编程中,wait
机制是一个非常重要的概念。它允许线程暂停执行,直到其他线程对该对象调用notify
或notifyAll
方法。这一机制在协调多个线程的执行顺序、避免资源竞争等方面发挥着关键作用。本文将深入探讨Java中wait
的基础概念、使用方法、常见实践以及最佳实践,帮助读者全面掌握这一重要的多线程控制技术。
目录
- 基础概念
- 使用方法
wait()
方法wait(long timeout)
方法wait(long timeout, int nanos)
方法
- 常见实践
- 生产者 - 消费者模型
- 线程同步与资源共享
- 最佳实践
- 避免死锁
- 合理设置等待时间
- 小结
- 参考资料
基础概念
在Java中,wait
方法是定义在Object
类中的实例方法。每个对象都有一个与之关联的监视器(monitor),也称为锁。当一个线程访问被synchronized
关键字修饰的代码块或方法时,它会获取该对象的锁。
wait
方法用于使当前线程等待,直到其他线程调用该对象的notify
或notifyAll
方法。调用wait
方法时,当前线程会释放对象的锁,进入等待状态,直到被唤醒。这就允许其他线程获取该对象的锁并对其进行操作。
使用方法
wait()
方法
这是最基本的wait
方法调用形式。调用该方法后,当前线程会释放对象的锁,并进入等待状态,直到其他线程调用该对象的notify
或notifyAll
方法。
public class WaitExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread 1 starts waiting...");
lock.wait();
System.out.println("Thread 1 has been awakened...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2 is doing some work...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2 notifies Thread 1...");
lock.notify();
}
});
thread1.start();
thread2.start();
}
}
wait(long timeout)
方法
这个方法允许指定一个最长等待时间(以毫秒为单位)。如果在指定的时间内没有其他线程调用notify
或notifyAll
方法,那么当前线程会自动唤醒。
public class WaitTimeoutExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread 1 starts waiting with timeout...");
lock.wait(5000);
System.out.println("Thread 1 has been awakened or timeout reached...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
}
}
wait(long timeout, int nanos)
方法
此方法在wait(long timeout)
的基础上,增加了对纳秒级别的时间控制。它允许更精确地指定等待时间。
public class WaitNanosExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread 1 starts waiting with nanos timeout...");
lock.wait(2000, 500000000);
System.out.println("Thread 1 has been awakened or timeout reached...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
}
}
常见实践
生产者 - 消费者模型
生产者 - 消费者模型是多线程编程中的经典示例,wait
和notify
机制在其中起到了关键作用。生产者线程生产数据并将其放入共享缓冲区,消费者线程从缓冲区中取出数据进行处理。当缓冲区满时,生产者线程需要等待;当缓冲区为空时,消费者线程需要等待。
import java.util.LinkedList;
import java.util.Queue;
class Producer implements Runnable {
private final Queue<Integer> queue;
private final int capacity;
public Producer(Queue<Integer> queue, int capacity) {
this.queue = queue;
this.capacity = capacity;
}
@Override
public void run() {
int value = 0;
while (true) {
synchronized (queue) {
while (queue.size() == capacity) {
try {
System.out.println("Queue is full, Producer is waiting...");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Producing value: " + value);
queue.add(value++);
queue.notify();
}
}
}
}
class Consumer implements Runnable {
private final Queue<Integer> queue;
public Consumer(Queue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.isEmpty()) {
try {
System.out.println("Queue is empty, Consumer is waiting...");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int value = queue.poll();
System.out.println("Consuming value: " + value);
queue.notify();
}
}
}
}
public class ProducerConsumerExample {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
int capacity = 5;
Thread producerThread = new Thread(new Producer(queue, capacity));
Thread consumerThread = new Thread(new Consumer(queue));
producerThread.start();
consumerThread.start();
}
}
线程同步与资源共享
在多个线程共享资源的场景下,wait
和notify
机制可以用于确保资源的正确访问和同步。例如,多个线程需要访问一个共享的数据库连接池,当连接池中的连接耗尽时,线程需要等待,直到有连接被释放并返回连接池。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class ConnectionPool {
private final BlockingQueue<Object> connections;
public ConnectionPool(int size) {
connections = new ArrayBlockingQueue<>(size);
for (int i = 0; i < size; i++) {
connections.add(new Object());
}
}
public Object getConnection() {
try {
return connections.take();
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
}
public void releaseConnection(Object connection) {
connections.add(connection);
}
}
class Worker implements Runnable {
private final ConnectionPool pool;
public Worker(ConnectionPool pool) {
this.pool = pool;
}
@Override
public void run() {
Object connection = pool.getConnection();
if (connection != null) {
try {
System.out.println(Thread.currentThread().getName() + " acquired connection");
// 模拟使用连接进行工作
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
pool.releaseConnection(connection);
System.out.println(Thread.currentThread().getName() + " released connection");
}
}
}
}
public class ConnectionPoolExample {
public static void main(String[] args) {
ConnectionPool pool = new ConnectionPool(3);
Thread worker1 = new Thread(new Worker(pool), "Worker1");
Thread worker2 = new Thread(new Worker(pool), "Worker2");
Thread worker3 = new Thread(new Worker(pool), "Worker3");
Thread worker4 = new Thread(new Worker(pool), "Worker4");
worker1.start();
worker2.start();
worker3.start();
worker4.start();
}
}
最佳实践
避免死锁
死锁是多线程编程中常见的问题,当两个或多个线程相互等待对方释放锁时,就会发生死锁。为了避免死锁,应该遵循以下原则:
- 尽量减少锁的使用范围,只在必要的代码块中使用synchronized
关键字。
- 按照固定的顺序获取锁,避免不同线程以不同顺序获取锁。
- 使用tryLock
方法来尝试获取锁,避免无限期等待。
合理设置等待时间
在使用带有超时参数的wait
方法时,应该根据具体业务需求合理设置等待时间。过短的等待时间可能导致线程频繁唤醒和重试,增加系统开销;过长的等待时间可能导致响应不及时,影响系统性能。
小结
本文深入探讨了Java中的wait
机制,包括其基础概念、使用方法、常见实践以及最佳实践。通过学习这些内容,读者应该能够在多线程编程中熟练运用wait
机制,实现线程之间的有效同步和协调,避免常见的多线程问题,提高程序的性能和稳定性。
参考资料
- Oracle Java Documentation - Object Class
- 《Effective Java》by Joshua Bloch
- 《Java Concurrency in Practice》by Brian Goetz et al.