在 Java 中,线程间的通信可以通过多种机制实现,以确保多个线程能够协调工作而不发生数据不一致或竞争条件的问题。以下是几种常见的线程间通信的方法:
1. 使用 wait()
, notify()
和 notifyAll()
这些方法是 Object
类的一部分,因此所有的 Java 对象都可以使用它们。它们通常与同步块(synchronized
)一起使用来控制对共享资源的访问。
wait()
: 让当前线程等待,并释放当前对象上的锁,直到其他线程调用此对象的notify()
或notifyAll()
方法。notify()
: 唤醒在此对象监视器上等待的一个线程。如果有多个线程都在等待,则随机选择一个唤醒。notifyAll()
: 唤醒在此对象监视器上等待的所有线程。
示例代码:
class Message {
private String content;
private boolean isAvailable = false;
public synchronized String read() {
while (!isAvailable) {
try {
wait(); // 当没有消息可读时,等待
} catch (InterruptedException e) {}
}
System.out.println("读取消息:" + content);
isAvailable = false; // 消息已被读取
notify(); // 通知写入线程可以写入新消息了
return content;
}
public synchronized void write(String content) {
while (isAvailable) {
try {
wait(); // 当前有未读的消息,等待
} catch (InterruptedException e) {}
}
this.content = content;
isAvailable = true;
System.out.println("写入消息:" + content);
notify(); // 通知读取线程可以读取消息了
}
}
2. 使用 volatile
关键字
volatile
变量提供了一种比锁更轻量级的方式来保证变量的可见性,即当一个线程修改了一个 volatile
变量的值时,新的值对于其他线程来说是立即可见的。但它不能保证原子性操作,因此适用于不需要复合操作的情况。
3. 使用 CountDownLatch
CountDownLatch
是一种同步工具类,它允许一个或多个线程等待,直到一组操作完成为止。通过计数器来控制线程的同步。
示例代码:
CountDownLatch latch = new CountDownLatch(1);
Thread thread1 = new Thread(() -> {
System.out.println("线程1正在等待...");
try {
latch.await(); // 等待计数器归零
} catch (InterruptedException e) {}
System.out.println("线程1继续执行");
});
Thread thread2 = new Thread(() -> {
System.out.println("线程2即将完成任务");
latch.countDown(); // 减少计数器
});
thread1.start();
thread2.start();
4. 使用 CyclicBarrier
CyclicBarrier
允许一组线程互相等待,直到到达某个公共屏障点。只有当所有参与线程都到达了这个点之后,它们才能继续往下执行。
5. 使用 Exchanger
Exchanger<V>
是一个用于两个线程之间交换数据的同步点。当两个线程都到达同步点时,它们可以交换数据。
6. 使用并发集合类
Java 提供了一些线程安全的集合类,如 ConcurrentHashMap
, CopyOnWriteArrayList
等,这些集合类内部实现了高效的并发控制机制,可以用来在线程间安全地传递信息。
7. 使用阻塞队列
BlockingQueue
是线程安全的队列接口,提供了插入、移除和检查元素的方法,这些方法会在无法执行对应操作时阻塞调用线程或者超时后返回特定值。常用于生产者-消费者模式中。
示例代码:
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 生产者
new Thread(() -> {
try {
queue.put("Hello World"); // 如果队列满,则等待
} catch (InterruptedException e) {}
}).start();
// 消费者
new Thread(() -> {
try {
String message = queue.take(); // 如果队列为空,则等待
System.out.println("收到消息:" + message);
} catch (InterruptedException e) {}
}).start();
每种方法都有其适用场景,选择哪种方式取决于具体的应用需求。理解这些机制可以帮助你设计出高效且可靠的多线程程序。
THE END