countdownlatch,cyclicbarrier,semaphore的区别,
1和2主要做线程同步的,1主要用于,一个线程等待其他线程执行完成,并且不能重用,2只要用于多个线程相互等待,做完之后,一起乡下,执行,可以重置,3,主要控制资源访问量的,三者,都是基于aqs实现的,要想实现多个线程之间相互同步,可以用reentrlock的多条件等待实现
Semaphore
① 拿许可证
acquire() ------ 阻塞拿,没许可就等着
tryAcquire() ------ 尝试拿,拿不到拉倒
acquireUninterruptibly() ------ 死等拿,天塌了也不停
② 还许可证
release() ------ 还一个,可跨线程还,会累加
③ 查状态
availablePermits() ------ 还剩几个
drainPermits() ------ 全部薅走
getQueueLength() ------ 多少人在等
hasQueuedThreads() ------ 有人在排队吗
java
package org.example;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 使用多个信号量控制多个线程顺序输出 1~100。
* 信号量数组通过静态代码块初始化,并预先释放第一个信号量。
*/
public class SequentialPrinter {
// 线程数量(可调整)
private static final int THREAD_COUNT = 3;
// 最大输出数字
private static final int MAX_NUMBER = 100;
// 每个线程对应的信号量(静态成员)
private static final Semaphore[] semaphores = new Semaphore[THREAD_COUNT];
// 当前待打印的数字(原子操作保证线程安全)
private static final AtomicInteger currentNumber = new AtomicInteger(1);
// 静态代码块:初始化所有信号量,并让第一个信号量获得一个许可
static {
for (int i = 0; i < THREAD_COUNT; i++) {
semaphores[i] = new Semaphore(0);
}
semaphores[0].release(); // 第 0 号线程可以先执行
}
/**
* 打印任务:每个线程持有一个自己的信号量和一个下一个线程的信号量
*/
static class Printer implements Runnable {
private final int threadId;
private final Semaphore mySemaphore;
private final Semaphore nextSemaphore;
public Printer(int threadId, Semaphore mySemaphore, Semaphore nextSemaphore) {
this.threadId = threadId;
this.mySemaphore = mySemaphore;
this.nextSemaphore = nextSemaphore;
}
@Override
public void run() {
while (true) {
try {
// 获取自己的信号量(没有许可则阻塞)
mySemaphore.acquire();
int num = currentNumber.getAndIncrement();
if (num <= MAX_NUMBER) {
System.out.println("Thread-" + threadId + " 输出: " + num);
// 唤醒下一个线程
nextSemaphore.release();
} else {
// 数字已超限,传递退出信号给下一个线程
nextSemaphore.release();
break;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
// 创建并启动线程
Thread[] threads = new Thread[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
Semaphore my = semaphores[i];
Semaphore next = semaphores[(i + 1) % THREAD_COUNT];
Printer printer = new Printer(i, my, next);
threads[i] = new Thread(printer, "Printer-" + i);
threads[i].start();
}
// 等待所有线程结束
for (Thread t : threads) {
t.join();
}
System.out.println("所有线程已退出,1~" + MAX_NUMBER + " 顺序输出完毕。");
}
}
CountDownLatch 是 Java 并发包(java.util.concurrent)中的一个同步工具类。它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
可以理解为一个倒计数器:初始计数设置为 N,每调用一次 countDown() 计数减 1,当计数减到 0 时,所有在 await() 上等待的线程被唤醒继续执行。
方法 说明
CountDownLatch(int count) 构造方法,指定初始计数(必须 ≥ 0)。如果 count = 0,则 await() 会立即通过。
void await() 使当前线程等待,直到计数变为 0。如果计数已为 0,则直接返回。可响应中断(抛出 InterruptedException)。
boolean await(long timeout, TimeUnit unit) 带超时的等待。如果超时时间内计数未变为 0,则返回 false;否则返回 true。
void countDown() 将计数减 1。如果减后计数变为 0,则唤醒所有在 await() 上等待的线程。
long getCount() 返回当前计数值(调试或监控用,不是同步安全的通常业务逻辑)。
注意:计数不能重置。当计数变为 0 后,后续 await() 立即通过,countDown() 不再生效。
示例一,主线程等待多个异步任务完成后再处理结果(一等多)
java
public class BatchProcess {
public static void main(String[] args) throws InterruptedException {
int taskCount = 5;
CountDownLatch latch = new CountDownLatch(taskCount);
ExecutorService executor = Executors.newFixedThreadPool(taskCount);
for (int i = 0; i < taskCount; i++) {
final int taskId = i;
executor.submit(() -> {
try {
// 模拟异步任务
Thread.sleep(1000);
System.out.println("任务 " + taskId + " 完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 无论成功/失败,都要减计数
}
});
}
// 主线程等待所有任务完成
latch.await();
System.out.println("所有任务已完成,开始汇总结果...");
executor.shutdown();
}
}
示例 2:多个线程同时开始执行(多等一,模拟并发)
java
public class ConcurrentStarter {
public static void main(String[] args) throws InterruptedException {
int workerCount = 10;
CountDownLatch startSignal = new CountDownLatch(1); // 控制起跑
CountDownLatch doneSignal = new CountDownLatch(workerCount); // 等待完成
for (int i = 0; i < workerCount; i++) {
new Thread(() -> {
try {
startSignal.await(); // 所有线程在此等待
// 执行并发任务
System.out.println(Thread.currentThread().getName() + " 开始工作");
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
doneSignal.countDown();
}
}).start();
}
System.out.println("准备就绪,3秒后所有线程同时启动...");
Thread.sleep(3000);
startSignal.countDown(); // 发令枪响
doneSignal.await(); // 主线程等待所有工作线程结束
System.out.println("全部工作完成");
}
}
示例 3:分布式系统 -- 等待多个服务健康检查完成
java
public class ServiceHealthChecker {
private static final List<String> SERVICES = Arrays.asList("DB", "Redis", "MQ", "ConfigCenter");
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(SERVICES.size());
for (String service : SERVICES) {
new Thread(() -> {
try {
// 模拟检查服务是否可用
boolean healthy = checkService(service);
if (healthy) {
System.out.println(service + " is healthy.");
} else {
System.err.println(service + " is NOT healthy.");
}
} finally {
latch.countDown();
}
}).start();
}
// 最多等待10秒,所有服务必须就绪
if (latch.await(10, TimeUnit.SECONDS)) {
System.out.println("所有服务检查完成,应用启动...");
} else {
System.err.println("某些服务未在10秒内响应,启动失败");
System.exit(1);
}
}
private static boolean checkService(String service) throws InterruptedException {
Thread.sleep(500); // 模拟网络请求
return true; // 简化示例
}
}
CyclicBarrier 是 Java 并发包(java.util.concurrent)中的一个同步辅助类,它允许一组线程互相等待,直到所有线程都到达某个公共屏障点(barrier)后,才能继续执行后续任务。
与 CountDownLatch 不同,CyclicBarrier 的计数器可以循环使用(Cyclic 意为"循环的")。
方法 说明
CyclicBarrier(int parties) 构造方法:设置需要等待的线程数量(parties)。
CyclicBarrier(int parties, Runnable barrierAction) 构造方法:当所有线程都到达屏障时,优先执行 barrierAction(由最后一个到达的线程执行)。
int await() 使当前线程等待,直到所有 parties 都调用了 await()。如果当前线程是最后一个到达的,则会唤醒所有等待线程(并可选执行 barrierAction)。返回当前线程到达的索引(0 表示最后一个)。可响应中断。
int await(long timeout, TimeUnit unit) 带超时的等待,超时后抛出 TimeoutException(其他等待线程会收到 BrokenBarrierException)。
void reset() 将屏障重置到初始状态(如果此时有线程在等待,会抛出 BrokenBarrierException)。
int getNumberWaiting() 返回当前在屏障处等待的线程数量。
boolean isBroken() 判断屏障是否被破坏(例如等待时超时或中断)。
示例 1:基本用法------多线程互相等待,一起继续
java
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
// 所有线程到达屏障后,由最后一个到达的线程执行此任务
System.out.println("所有线程已到达屏障,继续执行下一阶段...");
});
for (int i = 0; i < threadCount; i++) {
final int id = i;
new Thread(() -> {
try {
System.out.println("线程 " + id + " 开始执行第一阶段任务...");
Thread.sleep((long) (Math.random() * 1000));
System.out.println("线程 " + id + " 到达屏障");
barrier.await(); // 等待其他线程
System.out.println("线程 " + id + " 进入第二阶段");
Thread.sleep((long) (Math.random() * 1000));
System.out.println("线程 " + id + " 完成第二阶段");
// 屏障可重复使用(如果需要,再调用 barrier.await() 即可)
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
输出示例(顺序可能不同):
线程 0 开始执行第一阶段任务...
线程 1 开始执行第一阶段任务...
线程 2 开始执行第一阶段任务...
线程 1 到达屏障
线程 0 到达屏障
线程 2 到达屏障
所有线程已到达屏障,继续执行下一阶段...
线程 2 进入第二阶段
线程 1 进入第二阶段
线程 0 进入第二阶段
线程 0 完成第二阶段
线程 1 完成第二阶段
线程 2 完成第二阶段
示例 2:可重用的 CyclicBarrier(模拟多轮并发起跑)
java
public class ReusableBarrier {
public static void main(String[] args) {
int runners = 5;
CyclicBarrier startLine = new CyclicBarrier(runners, () -> System.out.println("--- 发令枪响,所有运动员起跑 ---"));
for (int i = 0; i < runners; i++) {
final int athlete = i;
new Thread(() -> {
try {
// 第一轮
System.out.println("运动员 " + athlete + " 到达起跑线,等待...");
startLine.await();
System.out.println("运动员 " + athlete + " 开始跑");
Thread.sleep(500);
System.out.println("运动员 " + athlete + " 第一轮结束");
// 第二轮(重用同一个 Barrier)
System.out.println("运动员 " + athlete + " 回到起跑线,等待第二轮...");
startLine.await();
System.out.println("运动员 " + athlete + " 第二轮开始跑");
Thread.sleep(500);
System.out.println("运动员 " + athlete + " 第二轮结束");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
示例 3:带超时的等待与重置
java
CyclicBarrier barrier = new CyclicBarrier(3);
try {
barrier.await(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
System.out.println("超时,屏障被破坏");
barrier.reset(); // 重置屏障,其他等待线程会抛出 BrokenBarrierException
}
ReentrantLock 常用方法三段式记忆法
① 拿锁
lock() ------ 阻塞拿,拿不到就等
tryLock() ------ 尝试拿,拿不到拉倒
tryLock(时间) ------ 限时等,超时放弃
lockInterruptibly() ------ 可中断拿,别人 interrupt 我就停
② 还锁
unlock() ------ 必须释放,成对出现,finally 里最稳
③ 高级功能
newCondition() ------ 创建条件变量,精确唤醒condition对象只有唤醒,阻塞两种方法
getHoldCount() ------ 当前线程重入次数
isHeldByCurrentThread() ------ 锁是不是我拿着
getQueueLength() ------ 等锁的有多少人
isFair() ------ 是否公平锁
用法
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
-
使用 ReentrantLock + Condition 控制多个线程顺序输出 1~100。
-
每个线程有一个对应的 Condition,通过锁和条件变量实现精确唤醒。
*/
public class SequentialPrinterWithLock {
// 线程数量(可调整)
private static final int THREAD_COUNT = 3;
// 最大输出数字
private static final int MAX_NUMBER = 100;
// 共享锁和条件数组
private static final ReentrantLock lock = new ReentrantLock();
private static final Condition\[\] conditions = new ConditionTHREAD_COUNT;
// 共享状态
private static int currentNumber = 1; // 下一个待输出的数字
private static int currentThreadId = 0; // 当前应该运行的线程编号 (0 ~ THREAD_COUNT-1)
// 静态代码块初始化所有 Condition
static {
for (int i = 0; i < THREAD_COUNT; i++) {
conditionsi = lock.newCondition();
}
}
/**
-
每个线程的任务:输出数字并唤醒下一个线程
*/
static class Printer implements Runnable {
private final int threadId;
public Printer(int threadId) {
this.threadId = threadId;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
// 如果不是当前线程应该运行的编号,则等待
while (currentThreadId != threadId) {
conditionsthreadId.await();
}
// 检查是否已经输出完所有数字 if (currentNumber > MAX_NUMBER) { // 通知下一个线程退出(避免死等) int nextId = (currentThreadId + 1) % THREAD_COUNT; conditions[nextId].signal(); break; } // 输出当前数字 System.out.println("Thread-" + threadId + " 输出: " + currentNumber); currentNumber++; // 切换到下一个线程 currentThreadId = (currentThreadId + 1) % THREAD_COUNT; // 唤醒下一个线程 conditions[currentThreadId].signal(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } finally { lock.unlock(); } }}
}
public static void main(String\[\] args) throws InterruptedException {
// 创建并启动所有线程
Thread\[\] threads = new ThreadTHREAD_COUNT;
for (int i = 0; i < THREAD_COUNT; i++) {
threadsi = new Thread(new Printer(i), "Printer-" + i);
threadsi.start();
}
// 等待所有线程结束 for (Thread t : threads) { t.join(); } System.out.println("所有线程已退出,1~" + MAX_NUMBER + " 顺序输出完毕。");}
}
-
| 特性 | CyclicBarrier | CountDownLatch |
|---|---|---|
| 可重用性 | 可重复使用 (调用 reset() 或自动重置) | 不可重用,计数器减到 0 后无法恢复 |
| 等待线程数 | 固定数量的线程互相等待(必须相等) | 主线程(或任意线程)等待其他 N 个线程完成 |
| 构造参数 | parties(参与的线程数) | count(需要 countDown 的次数) |
| 计数方式 | 调用 await() 计数减 1(到达屏障) | 调用 countDown() 计数减 1 |
| 屏障动作 | 支持 barrierAction(最后一个到达的线程执行) | 无 |
| 异常处理 | 一个线程中断/超时会破坏屏障,其他线程收到 BrokenBarrierException | 单个线程中断只影响该线程,不影响其他线程 |
| 典型场景 | 多线程分阶段计算、并行迭代、可重用的并发起跑 | 主线程等待多个子任务完成、多等一、一等多 |
reentrantlock和synchronized的区别
synchronized是jvm层面的锁,通过监视器monitor对象实现,在要加锁的字节码前后加上monitorenter,monitorexit字节码指令来实现,自动加锁解锁,默认非公平
ReentrantLock是API级别实现,需要显式lock()和unlock(),但支持公平锁、可中断锁获取、尝试获取锁和多个条件变量。
性能上Java是1.6后两者相当,sync通过锁升级优化了性能
选择时建议:简单场景用synchronized保持代码简洁,
当需要可中断性、超时控制、公平性或多条件等待等高级功能时选用ReentrantLock。使用时必须将unlock()放在finally块中确保锁释放,避免死锁。
在synchronized锁字符串时,
1.字符串可能会被jvm放入常量池,就是内容相同的字符串字面量在内存中只有一个,如果多个锁锁同一对象可能相互干扰,
2.new string("lock")每次产生新的对象,锁的就不是一个对象,无法互斥,
3.锁对象引用被重新赋值后,后续对象使用新锁,锁被破坏,
4.行为不确定性 拼接、intern() 等操作使锁对象的身份难以判断
5.synchronized 的锁对象。最安全、清晰的做法是使用专门的 private final Object 实例作为锁。这样不仅能避免上述陷阱,