CountDownLatch、CyclicBarrier、Semaphore
1. CountDownLatch
-
CountDownLatch
是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。 -
CountDownLatch
的作用就是 允许 taskNum 个线程阻塞在一个地方,直至所有线程的任务都执行完毕。在文件分片下载的操作中,多个线程分别执行各自的下载任务,使用CountDownLatch
等待下载完成再进行文件合并。 -
现在有个场景:
-
我们要读取处理 6 个文件(任务),这 6 个任务都是没有执行顺序依赖的任务,但是我们需要返回给用户的时候将这几个文件的处理的结果进行统计整理。
-
为此我们定义了一个线程池和 taskNum 为 6 的
CountDownLatch
对象 。使用线程池处理读取任务,每一个线程处理完之后就将 taskNum - 1,调用CountDownLatch
对象的await()
方法,直到所有文件读取完之后,才会接着执行后面的逻辑。
-
-
只能使用一次
private static final int taskNum = 6;
public static void main(String[] args) throws InterruptedException {
// 创建指定数量的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
CountDownLatch countDownLatch = new CountDownLatch(taskNum);
for (int i = 1; i <= taskNum; i++) {
final int idx = i;
threadPool.execute(() -> {
try {
// 正常的业务
System.out.println("当前正在处理任务: " + idx);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 让计数器减1,即表示当前的任务已经被执行完毕
countDownLatch.countDown();
}
});
}
// 等待所有的任务执行完毕再往下执行
countDownLatch.await();
System.out.println("所有的任务都执行完毕!");
// 关闭线程池
threadPool.shutdown();
}
2. CyclicBarrier
CyclicBarrier
和CountDownLatch
非常类似,它也可以实现线程间的技术等待,但是它的功能比CountDownLatch
更加复杂和强大。主要应用场景和CountDownLatch
类似。CyclicBarrier
的字面意思是可循环使用(Cyclic
)的屏障(Barrier
)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier
默认的构造方法是CyclicBarrier(int parties)
,其参数表示屏障拦截的线程数量,每个线程调用await()
方法告诉CyclicBarrier
我已经到达了屏障,然后当前线程被阻塞。- 可以重复使用
private static final int taskNum = 6;
public static void main(String[] args) throws InterruptedException {
// 创建指定数量的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
// 可以看成是累加器,等所有人来了之后在进行后续的操作
CyclicBarrier cyclicBarrier = new CyclicBarrier(taskNum, () -> {
System.out.println("所有人都到齐了,可以开始了"); // 表示等所有任务完成之后需要做的事情
});
for (int i = 1; i <= taskNum; i++) {
final int num = i;
threadPool.execute(() -> {
try {
System.out.println( num + " 号运行员已经准备好了.......");
}catch (Exception e){
e.printStackTrace();
}
try {
// 调用await方法,告知cyc当前线程已经到达指定点,但是此时人未齐,所以会被阻塞
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
});
}
// 关闭线程池
threadPool.shutdown();
}
3. Semaphore
- 允许多个线程同时访问:
synchronized
和ReentrantLock
都是一次只允许一个线程访问某个资源,Semaphore
(信号量)可以指定多个线程同时访问某个资源。 - 信号量主要用于两个目的:一个是用于多个共享资源的互斥使用,另一个用于并发线程数控制。
private static final int taskNum = 6;
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
// 假设停车场只有3个停车位,此时有6辆车来竞争停车位
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= taskNum; i++) {
final int num = i;
threadPool.execute(() -> {
try {
// 线程获取到锁
semaphore.acquire();
System.out.println(num + " 号车获取到停车位.......");
// 给每辆车生成随机的停车时间
TimeUnit.SECONDS.sleep(new Random().nextInt(3));
System.out.println(num + " 号车已经离开了停车位------");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放所持有的锁
semaphore.release();
}
});
}
threadPool.shutdown();
}