目录
三、CountDownLatch与CyclicBarrier 区别
一、CountDownLatch
1、定义
用来协调线程间的同步,起到线程之间的通信作用的工具类。它主要又两个方法,当一个或多个线程调用await方法时,调用线程会被阻塞。其他线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),当计数器的值变为0时,因调用await方法被阻塞的线程会被唤醒,继续执行。
它能够使一个线程再等待一些线程完成各自工作后,再继续执行。使用一个计数器实现,计数器初始值为线程的数量,当每一个线程完成自己任务后,计数器的值会减1,当计数器的值为0时,表示所有的线程都完成了任务,然后CountDownLatch上等待的线程就可以恢复执行任务。
2、应用场景
- 某一个线程再开始运行前等待n个线程执行完毕,如启动一个服务时,主线程需要等待多个组件加载完毕,之后再执行。
- 实现多个线程开始执行任务的最大并行性,注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。做法是初始化一个共享的CountDownLatch(1),将其计数器初始化为1,多个线程在开始执行任务前首先 coundownlatch.await(),让所有线程都在这个锁上等待,当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
3、程序代码
CountDownLatch是一次性的,计数器的值只能再构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。
package com.study.thread;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
private static CountDownLatch countDownLatch = new CountDownLatch(7);
private void runCountDown() {
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + "课程通过");
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 7; i++) {
new Thread(() -> {
CountDownLatchDemo demo = new CountDownLatchDemo();
demo.runCountDown();
}, i + "").start();
}
countDownLatch.await();
new Thread(() -> {
System.out.println("考试通过");
}, "BB").start();
}
}
运行结果:
0课程通过
5课程通过
2课程通过
6课程通过
3课程通过
4课程通过
1课程通过
考试通过
二、CyclicBarrier
1、定义
字面意思是可循环(Cyclic)使用的屏障(Barrier),它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。
CyclicBarrier类是concurrent并发包下的一工具类。
线程间同步阻塞是使用的是ReentrantLock,可重入锁
2、应用场景
- CyclicBarrier可以用于多线程计算数据,最后合并计算结果的应用场景
3、程序代码
package com.study.thread;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("收集完成,兑换大奖");
});
for (int i = 0; i < 7; i++) {
new Thread(() -> {
System.out.println("收集卡片" + Thread.currentThread().getName());
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, i + "").start();
}
}
}
运行结果
收集卡片0
收集卡片6
收集卡片5
收集卡片4
收集卡片3
收集卡片2
收集卡片1
收集完成,兑换大奖
三、CountDownLatch与CyclicBarrier 区别
1、CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次
2、CyclicBarrier还提供getNumberWaiting(可以获得CyclicBarrier阻塞的线程数量)、isBroken(用来知道阻塞的线程是否被中断)等方法。
3、CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。
四、Semaphore
1、定义
信号量主要用于两个目的,一个是用于多个共享资源的互斥使用(单个信号量的Semaphore对象可以实现互斥锁的功能),另一个用于并发线程数的控制,synchronized和重入锁ReentrantLock,这2种锁一次都只能允许一个线程访问一个资源,而信号量可以控制有多少个线程可以访问特定的资源。
Semaphore内部维护了一组虚拟的许可,许可的数量可以通过构造函数的参数指定。
- 访问特定资源前,必须使用acquire方法获得许可,如果许可数量为0,该线程则一直阻塞,直到有可用许可。
- 访问资源后,使用release释放许可。
- Semaphore和ReentrantLock类似,获取许可有公平策略和非公平许可策略,默认情况下使用非公平策略
2、应用场景
Semaphore可以用来做流量分流,特别是对公共资源有限的场景,比如数据库连接。
假设有这个的需求,读取几万个文件的数据到数据库中,由于文件读取是IO密集型任务,可以启动几十个线程并发读取,但是数据库连接数只有10个,这时就必须控制最多只有10个线程能够拿到数据库连接进行操作。这个时候,就可以使用Semaphore做流量控制。
3、程序代码
package com.study.thread;
import java.util.Arrays;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
//数据库连接数只有3个
Semaphore semaphore = new Semaphore(3);
//模拟有7个文件
for (int i = 0; i < 7; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "获取数据库连接");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + ">>>>>>释放数据库连接");
semaphore.release();
}
}, i + "").start();
}
}
}
运行结果:
0获取数据库连接
1获取数据库连接
2获取数据库连接
0>>>>>>释放数据库连接
2>>>>>>释放数据库连接
1>>>>>>释放数据库连接
3获取数据库连接
5获取数据库连接
4获取数据库连接
3>>>>>>释放数据库连接
5>>>>>>释放数据库连接
4>>>>>>释放数据库连接
6获取数据库连接
6>>>>>>释放数据库连接