Java中,线程同步与通信有多种工具,如对象监视器(锁)提供的 wait()
, notify()
, 和 notifyAll()。
ReentrantLock提供的 Condition.await/signalAll等。
常见的还有 CountDownLatch
、CyclicBarrier
、Semaphore
、Phaser
和 Exchanger
,它们各自解决了不同的并发问题。CountDownLatch
和 CyclicBarrier
主要用于线程间的同步等待;Semaphore
用于控制对资源的并发访问;Phaser
提供了更灵活的线程同步机制,适用于复杂的并行任务;而 Exchanger
则适用于两个线程之间的数据交换。
本文基于目前主流的面试题“多线程顺序打印”开始,为大家讲述如何用Java多线程实现。并讲述CountDownLatch
、CyclicBarrier
、Semaphore的作用与用法。(本文:含有完整的源码可直接运行测试)
一、使用wait、notifyAll
wait()
, notify()
, 和 notifyAll()
是三个用于线程间通信的关键方法,它们都定义在Object
类中,因此任何Java对象都可以调用它们。这些方法用于在对象监视器(monitor)上等待或唤醒线程
1.1 用法解释
wait()
- 功能:
wait()
方法是一个实例方法,它必须被对象的实例调用,并且调用它的线程必须持有该对象的锁。使当前线程等待,直到其他线程调用此对象的notify()
方法或notifyAll()
方法。此线程会被唤醒并重新竞争锁 - 注意:
wait()
方法必须在同步代码块或同步方法中调用,否则会抛出IllegalMonitorStateException
。
notify()
- 功能:唤醒在此对象监视器(锁)上等待的单个线程。选择哪个线程被唤醒是任意的,取决于JVM的实现。
- 注意:
notify()
方法也必须在同步代码块或同步方法中调用。
notifyAll()
- 功能:唤醒在此对象监视器(锁)上等待的所有线程。
- 注意:虽然
notifyAll()
会唤醒所有等待的线程,但只有成功获得对象锁的线程才能继续执行。
1.2 代码实现:N 个线程分别各自顺序打印 1到 N,每个线程输出1个
public class SequentialThreads {
private static final Object lock = new Object();
private volatile static int num = 1;
public static void main(String[] args) throws InterruptedException {
// N 个线程分别各自顺序打印 1到 N,每个线程输出1个
int size = 99;
List<Thread> list = new ArrayList<>(size);
for(int i = 1; i < size; i++) {
list.add(new Thread(new Task(i)));
}
for(Thread thread : list) {
thread.start();
}
for(Thread thread : list) {
thread.join();
}