Java中的线程通信详解
在多线程编程中,线程之间的通信是一个重要且复杂的主题。为了确保多线程程序的正确性和效率,线程需要能够互相通信并协调工作。Java 提供了多种方式来实现线程间的通信,如 wait()
、notify()
、notifyAll()
方法,以及更高级的并发工具类。本文将详细介绍 Java 中的线程通信,涵盖基本概念、常用方法和具体代码示例。
一、基本概念
线程通信是指线程之间通过某种机制传递信息,以协调它们之间的行为。Java 中提供了内置的线程通信机制,包括 wait()
、notify()
和 notifyAll()
方法,这些方法都是 Object
类的一部分,每个对象都可以作为线程通信的锁。
二、wait()
、notify()
和notifyAll()
方法
wait()
方法
wait()
方法使当前线程进入等待状态,直到其他线程调用 notify()
或 notifyAll()
方法唤醒它。该方法必须在同步块或同步方法中调用,否则会抛出 IllegalMonitorStateException
异常。
notify()
方法
notify()
方法唤醒一个正在等待该对象监视器的线程。如果有多个线程在等待,则选择其中一个线程唤醒。被唤醒的线程在获取对象的监视器后,才能继续执行。
notifyAll()
方法
notifyAll()
方法唤醒所有正在等待该对象监视器的线程。被唤醒的线程在获取对象的监视器后,才能继续执行。
三、代码示例
以下示例演示了如何使用 wait()
、notify()
和 notifyAll()
方法进行线程通信。
1. 生产者-消费者模型
package cn.juwatech.threads;
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumerExample {
private static final int CAPACITY = 5;
private final Queue<Integer> queue = new LinkedList<>();
private final Object lock = new Object();
public static void main(String[] args) {
ProducerConsumerExample example = new ProducerConsumerExample();
Thread producerThread = new Thread(example.new Producer());
Thread consumerThread = new Thread(example.new Consumer());
producerThread.start();
consumerThread.start();
}
class Producer implements Runnable {
@Override
public void run() {
int value = 0;
while (true) {
synchronized (lock) {
while (queue.size() == CAPACITY) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
queue.offer(value);
System.out.println("Produced: " + value);
value++;
lock.notify();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
while (true) {
synchronized (lock) {
while (queue.isEmpty()) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
int value = queue.poll();
System.out.println("Consumed: " + value);
lock.notify();
}
}
}
}
}
在这个示例中,生产者线程生成整数并将其放入队列中,而消费者线程从队列中取出整数并进行消费。wait()
和 notify()
方法用于协调生产者和消费者之间的工作。
四、使用更高级的并发工具类
Java 提供了更高级的并发工具类,如 BlockingQueue
、Semaphore
、CountDownLatch
和 CyclicBarrier
,它们简化了线程间的通信和同步。
1. 使用 BlockingQueue
实现生产者-消费者模型
package cn.juwatech.threads;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueExample {
private static final int CAPACITY = 5;
private final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(CAPACITY);
public static void main(String[] args) {
BlockingQueueExample example = new BlockingQueueExample();
Thread producerThread = new Thread(example.new Producer());
Thread consumerThread = new Thread(example.new Consumer());
producerThread.start();
consumerThread.start();
}
class Producer implements Runnable {
@Override
public void run() {
int value = 0;
while (true) {
try {
queue.put(value);
System.out.println("Produced: " + value);
value++;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
while (true) {
try {
int value = queue.take();
System.out.println("Consumed: " + value);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}
在这个示例中,BlockingQueue
处理了同步和通信逻辑,使得代码更加简洁和易于理解。
2. 使用 Semaphore
控制线程访问
Semaphore
是一种计数信号量,用于控制同时访问特定资源的线程数量。
package cn.juwatech.threads;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final int PERMITS = 3;
private final Semaphore semaphore = new Semaphore(PERMITS);
public static void main(String[] args) {
SemaphoreExample example = new SemaphoreExample();
for (int i = 0; i < 10; i++) {
new Thread(example.new Worker(i)).start();
}
}
class Worker implements Runnable {
private final int workerId;
Worker(int workerId) {
this.workerId = workerId;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("Worker " + workerId + " is working...");
Thread.sleep(2000);
System.out.println("Worker " + workerId + " finished working.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
}
}
}
}
在这个示例中,Semaphore
控制同时最多只有三个线程可以访问资源,其余线程必须等待。
五、线程通信的最佳实践
- 避免忙等待:使用
wait()
、notify()
、notifyAll()
或并发工具类避免忙等待,提高效率。 - 适当使用锁和同步:确保共享资源的安全访问,避免死锁。
- 选择合适的并发工具类:根据具体需求选择合适的并发工具类,简化线程通信和同步逻辑。
通过以上内容,我们详细介绍了 Java 中的线程通信机制,包括基本概念、常用方法及其具体代码示例,并探讨了使用更高级的并发工具类实现线程通信的方法。掌握这些知识,能够帮助我们编写出更加高效和可靠的多线程程序。