Java Thread Waiting:深入理解与实践
简介
在Java多线程编程中,线程等待(Thread Waiting)是一个至关重要的概念。它允许线程暂停执行,等待特定条件满足后再继续执行。这一机制在解决线程间的同步和协调问题时发挥着关键作用,能够有效避免竞争条件和死锁等常见问题,从而提升程序的稳定性和性能。本文将详细介绍Java线程等待的基础概念、使用方法、常见实践以及最佳实践,帮助读者全面掌握这一技术。
目录
- 基础概念
- 什么是线程等待
- 线程状态与等待
- 使用方法
Object
类的等待方法Condition
接口的使用
- 常见实践
- 生产者 - 消费者模型
- 线程池中的线程等待
- 最佳实践
- 避免死锁
- 合理设置等待时间
- 使用
CountDownLatch
和CyclicBarrier
- 小结
- 参考资料
基础概念
什么是线程等待
线程等待是指一个线程暂停其执行,进入等待状态,直到满足特定条件。这通常发生在多个线程需要共享资源或协调执行顺序的场景中。例如,一个线程可能需要等待另一个线程完成某个任务后才能继续执行。
线程状态与等待
在Java中,线程有多种状态,如NEW
(新建)、RUNNABLE
(可运行)、BLOCKED
(阻塞)、WAITING
(等待)、TIMED_WAITING
(计时等待)和TERMINATED
(终止)。当线程调用等待方法时,它会从RUNNABLE
状态转换到WAITING
或TIMED_WAITING
状态。处于WAITING
状态的线程会无限期等待,直到被其他线程唤醒;而处于TIMED_WAITING
状态的线程会等待指定的时间,时间结束后如果没有被唤醒也会继续执行。
使用方法
Object
类的等待方法
Object
类提供了三个用于线程等待的方法:wait()
、wait(long timeout)
和wait(long timeout, int nanos)
。
public class ObjectWaitExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread 1 is waiting...");
lock.wait();
System.out.println("Thread 1 has been awakened.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(2000);
System.out.println("Thread 2 is notifying...");
lock.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
}
在上述代码中,thread1
调用lock.wait()
进入等待状态,thread2
在睡眠2秒后调用lock.notify()
唤醒thread1
。需要注意的是,wait()
方法必须在synchronized
块中调用,否则会抛出IllegalMonitorStateException
。
Condition
接口的使用
Condition
接口是Java 5.0引入的,它提供了比Object
类等待方法更灵活的线程等待控制。Condition
与Lock
配合使用,一个Lock
可以创建多个Condition
实例。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread thread1 = new Thread(() -> {
lock.lock();
try {
System.out.println("Thread 1 is waiting...");
condition.await();
System.out.println("Thread 1 has been awakened.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
Thread thread2 = new Thread(() -> {
lock.lock();
try {
Thread.sleep(2000);
System.out.println("Thread 2 is notifying...");
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
thread1.start();
thread2.start();
}
}
在这个例子中,thread1
调用condition.await()
进入等待状态,thread2
在睡眠2秒后调用condition.signal()
唤醒thread1
。Condition
接口提供了更丰富的方法,如signalAll()
可以唤醒所有等待的线程。
常见实践
生产者 - 消费者模型
生产者 - 消费者模型是线程等待的经典应用场景。生产者线程生成数据并放入缓冲区,消费者线程从缓冲区取出数据进行处理。当缓冲区满时,生产者线程需要等待;当缓冲区空时,消费者线程需要等待。
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumerExample {
private static final int MAX_SIZE = 5;
private static Queue<Integer> queue = new LinkedList<>();
public static void main(String[] args) {
Thread producerThread = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
synchronized (queue) {
while (queue.size() == MAX_SIZE) {
try {
System.out.println("Producer is waiting...");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.add(i);
System.out.println("Producer produced: " + i);
queue.notify();
}
}
});
Thread consumerThread = new Thread(() -> {
while (true) {
synchronized (queue) {
while (queue.isEmpty()) {
try {
System.out.println("Consumer is waiting...");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int item = queue.poll();
System.out.println("Consumer consumed: " + item);
queue.notify();
}
}
});
producerThread.start();
consumerThread.start();
}
}
在这个例子中,生产者线程和消费者线程通过synchronized
块和wait()
、notify()
方法实现了缓冲区的同步访问。
线程池中的线程等待
线程池中的线程在任务队列没有任务时会进入等待状态,直到有新的任务提交。以下是一个简单的线程池示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(() -> {
System.out.println("Task 1 is running.");
});
executorService.submit(() -> {
System.out.println("Task 2 is running.");
});
executorService.shutdown();
}
}
在上述代码中,线程池中的线程在执行完任务后会等待新的任务提交,直到线程池关闭。
最佳实践
避免死锁
死锁是多线程编程中常见的问题,当两个或多个线程相互等待对方释放资源时就会发生死锁。为了避免死锁,应遵循以下原则: - 尽量减少锁的使用范围。 - 按照相同的顺序获取锁。 - 设置合理的锁获取超时时间。
合理设置等待时间
在使用wait(long timeout)
或Condition.await(long timeout, TimeUnit unit)
等方法时,应根据实际情况合理设置等待时间。如果等待时间过短,可能导致线程频繁唤醒,增加系统开销;如果等待时间过长,可能导致程序响应缓慢。
使用CountDownLatch
和CyclicBarrier
CountDownLatch
和CyclicBarrier
是Java并发包中提供的同步工具类,它们可以更方便地实现线程间的等待和同步。CountDownLatch
用于让一个或多个线程等待其他线程完成一组操作,CyclicBarrier
用于让一组线程相互等待,直到所有线程都到达某个屏障点。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) {
int numThreads = 3;
CountDownLatch latch = new CountDownLatch(numThreads);
for (int i = 0; i < numThreads; i++) {
new Thread(() -> {
try {
Thread.sleep((long) (Math.random() * 2000));
System.out.println(Thread.currentThread().getName() + " has finished.");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
try {
System.out.println("Main thread is waiting...");
latch.await();
System.out.println("All threads have finished.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int numThreads = 3;
CyclicBarrier barrier = new CyclicBarrier(numThreads, () -> {
System.out.println("All threads have reached the barrier.");
});
for (int i = 0; i < numThreads; i++) {
new Thread(() -> {
try {
Thread.sleep((long) (Math.random() * 2000));
System.out.println(Thread.currentThread().getName() + " is waiting at the barrier.");
barrier.await();
System.out.println(Thread.currentThread().getName() + " has passed the barrier.");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
小结
本文详细介绍了Java线程等待的基础概念、使用方法、常见实践以及最佳实践。通过合理运用线程等待机制,能够有效解决多线程编程中的同步和协调问题,提升程序的性能和稳定性。在实际开发中,应根据具体需求选择合适的等待方法,并遵循最佳实践原则,避免出现死锁等问题。
参考资料
- Oracle Java Documentation
- 《Effective Java》by Joshua Bloch
- 《Java Concurrency in Practice》by Brian Goetz et al.