前言
在 Java 多线程编程中,线程之间的协调和同步是非常关键的部分。为了简化线程间的协作,Java 5 引入了 java.util.concurrent
包,其中包含了多个同步工具类(Synchronizers),这些工具类可以帮助我们更方便地控制线程的执行顺序、等待条件和资源访问。
本文将详细介绍 java.util.concurrent
包中常用的同步工具类,包括:
CountDownLatch
CyclicBarrier
Phaser
Semaphore
Exchanger
我们将通过示例代码详细说明它们的使用方法、适用场景以及与 synchronized
和 ReentrantLock
的区别。
一、CountDownLatch(倒计时门闩)
简介
CountDownLatch
是一个同步辅助类,它允许一个或多个线程等待,直到其他线程完成一组操作。它内部维护一个计数器,当计数器减到 0 时,所有等待的线程被释放。
构造函数
public CountDownLatch(int count)
核心方法
await()
:当前线程等待,直到计数器减到 0。countDown()
:将计数器减 1。
使用场景
- 启动多个线程后,主线程等待所有线程完成任务。
- 初始化完成后,再开始执行后续操作。
示例代码
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 1; i <= threadCount; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 正在处理任务...");
Thread.sleep(2000); // 模拟耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 任务完成,计数器减一
}
}, "Thread-" + i).start();
}
System.out.println("主线程等待所有子线程完成...");
latch.await(); // 主线程在此阻塞,直到计数器为0
System.out.println("所有子线程已完成,主线程继续执行...");
}
}
输出示例
主线程等待所有子线程完成...
Thread-1 正在处理任务...
Thread-2 正在处理任务...
Thread-3 正在处理任务...
所有子线程已完成,主线程继续执行...
二、CyclicBarrier(循环屏障)
简介
CyclicBarrier
允许一组线程互相等待,直到所有线程都到达某个公共屏障点(barrier point)。与 CountDownLatch
不同的是,CyclicBarrier
是可以重用的。
构造函数
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
核心方法
await()
:线程调用此方法表示到达屏障点,进入等待,直到所有线程都到达。
使用场景
- 多线程协同执行任务,每轮任务结束后进行汇总或处理。
- 游戏中多个玩家准备就绪后开始游戏。
示例代码
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
System.out.println("所有线程已到达屏障点,开始汇总任务...");
});
for (int i = 1; i <= threadCount; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 正在执行任务...");
Thread.sleep((long) (Math.random() * 2000));
System.out.println(Thread.currentThread().getName() + " 到达屏障点");
barrier.await(); // 等待其他线程
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, "Thread-" + i).start();
}
}
}
输出示例
Thread-1 正在执行任务...
Thread-2 正在执行任务...
Thread-3 正在执行任务...
Thread-3 到达屏障点
Thread-1 到达屏障点
Thread-2 到达屏障点
所有线程已到达屏障点,开始汇总任务...
三、Phaser(阶段同步器)
简介
Phaser
是 Java 7 引入的一个更灵活的同步工具,它支持动态注册线程、分阶段同步等特性,功能比 CountDownLatch
和 CyclicBarrier
更强大。
核心概念
- Phase(阶段):每个阶段可以有多个线程参与。
- Arrive(到达):线程到达一个阶段。
- Advance(推进):所有线程到达后自动进入下一阶段。
核心方法
register()
:注册一个线程。arrive()
:线程到达当前阶段。arriveAndAwaitAdvance()
:线程到达并等待其他线程推进到下一阶段。arriveAndDeregister()
:到达并注销线程。
使用场景
- 多阶段任务的协同执行。
- 动态添加线程的场景。
示例代码
import java.util.concurrent.Phaser;
public class PhaserExample {
public static void main(String[] args) {
Phaser phaser = new Phaser();
phaser.register(); // 主线程注册
for (int i = 1; i <= 3; i++) {
phaser.register(); // 每个线程注册
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始阶段 0");
phaser.arriveAndAwaitAdvance(); // 等待所有线程到达
System.out.println(Thread.currentThread().getName() + " 开始阶段 1");
phaser.arriveAndAwaitAdvance();
System.out.println(Thread.currentThread().getName() + " 开始阶段 2");
phaser.arriveAndDeregister(); // 完成任务并注销
}, "Thread-" + i).start();
}
phaser.arriveAndAwaitAdvance(); // 主线程也参与阶段同步
System.out.println("所有线程完成阶段 0");
phaser.arriveAndAwaitAdvance();
System.out.println("所有线程完成阶段 1");
phaser.arriveAndDeregister(); // 主线程注销
System.out.println("Phaser 完成");
}
}
四、Semaphore(信号量)
简介
Semaphore
控制同时访问的线程数量,可以用来实现资源池、连接池、限流等机制。它维护一组许可(permits),线程通过 acquire()
获取许可,通过 release()
释放许可。
构造函数
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
核心方法
acquire()
:获取一个许可,如果没有许可可用,线程将阻塞。release()
:释放一个许可。
使用场景
- 控制资源并发访问数量。
- 实现限流、流量控制。
示例代码
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
int permits = 2;
Semaphore semaphore = new Semaphore(permits);
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 等待获取许可...");
semaphore.acquire(); // 获取许可
System.out.println(Thread.currentThread().getName() + " 获取许可,开始执行任务...");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " 释放许可");
semaphore.release(); // 释放许可
}
}, "Thread-" + i).start();
}
}
}
输出示例
Thread-1 等待获取许可...
Thread-1 获取许可,开始执行任务...
Thread-2 等待获取许可...
Thread-2 获取许可,开始执行任务...
Thread-3 等待获取许可...
Thread-4 等待获取许可...
Thread-5 等待获取许可...
Thread-1 释放许可
Thread-3 获取许可,开始执行任务...
Thread-2 释放许可
Thread-4 获取许可,开始执行任务...
Thread-3 释放许可
Thread-5 获取许可,开始执行任务...
Thread-4 释放许可
Thread-5 释放许可
五、Exchanger(线程交换器)
简介
Exchanger
是一个用于两个线程之间交换数据的同步工具。它提供了一个同步点,在这个点上两个线程可以交换数据。
核心方法
exchange(V data)
:线程在此方法调用时阻塞,直到另一个线程也调用该方法,两者交换数据。
使用场景
- 线程间数据交换。
- 双缓冲队列实现。
示例代码
import java.util.concurrent.Exchanger;
public class ExchangerExample {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new Thread(() -> {
String data = "来自线程 A 的数据";
System.out.println("线程 A 发送数据: " + data);
try {
String response = exchanger.exchange(data);
System.out.println("线程 A 收到响应: " + response);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Thread-A").start();
new Thread(() -> {
String data = "来自线程 B 的数据";
System.out.println("线程 B 发送数据: " + data);
try {
String response = exchanger.exchange(data);
System.out.println("线程 B 收到响应: " + response);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Thread-B").start();
}
}
输出示例
线程 A 发送数据: 来自线程 A 的数据
线程 B 发送数据: 来自线程 B 的数据
线程 B 收到响应: 来自线程 A 的数据
线程 A 收到响应: 来自线程 B 的数据
六、总结对比
工具类 | 用途 | 可重用 | 特点 |
---|---|---|---|
CountDownLatch | 等待一组线程完成 | 不可重用 | 一次性使用 |
CyclicBarrier | 线程相互等待,可重复使用 | 可重用 | 可指定屏障动作 |
Phaser | 多阶段同步,动态注册线程 | 可重用 | 使用方式更灵活 |
Semaphore | 控制并发访问数量 | 可重用 | 支持公平与非公平模式 |
Exchanger | 两个线程之间交换数据 | 可重用 | 实现简单且高效 |
七、与 synchronized 和 ReentrantLock 的区别
对比项 | synchronized | ReentrantLock | 同步工具类 |
---|---|---|---|
使用方式 | 使用关键字实现 | 需要显式加锁和释放 | 作为辅助类使用 |
可重入性 | 支持 | 支持 | 依具体实现而定 |
超时控制 | 不支持 | 支持 | 支持 |
尝试获取锁 | 不支持 | 支持 | 支持 |
多线程协作 | 不支持 | 不支持 | 支持 |
八、结语
Java 并发包中的同步工具类极大地简化了多线程程序的开发,每种工具都有其适用的场景和优势。在实际开发中,应根据业务需求选择合适的同步机制,避免使用不当导致的死锁、资源竞争等问题。
希望本篇博客能帮助你更好地理解和使用 Java 中的同步工具类!如果你有任何问题或建议,欢迎留言讨论。