CounDownLatch、CyclicBarrier、Semaphore
CountDownLatch
CountDownLatch是在java1.5被引入,跟它一起被引入的工具类还有CyclicBarrier、Semaphore、concurrentHashMap和BlockingQueue。
CountDownLatch概念
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。
CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。
CountDownLatch的用法
CountDownLatch典型用法:1、某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
CountDownLatch典型用法:2、实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。
// 当一个任务需要多任务同时进行,提高效率,缩短串行执行时间。cdl.await最后等待位置
public static void main(String[] args) {
CountDownLatch cdl = new CountDownLatch(3);
ExecutorService executor = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>());
for (int i = 0; i < 3; i++) {
executor.execute(() -> {cdl.countDown(); System.out.println(String.valueOf(1111));});
}
try {
cdl.await();
System.out.println("线程结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
}
CyclicBarrier
CyclicBarrier栅栏类似于赛跑同一起跑线
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
System.out.println("栅栏中的线程执行完成");
});
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(() -> {
try {
cyclicBarrier.await();
System.out.println("线程1:" + Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
});
executorService.submit(() -> {
try {
cyclicBarrier.await();
System.out.println("线程2:" + Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
});
executorService.shutdown();
}
例子2
public class TravelTask implements Runnable{
private final CyclicBarrier cyclicBarrier;
private final String name;
/**
* 赶到的时间
*/
private final int arriveTime;
public TravelTask(CyclicBarrier cyclicBarrier, String name, int arriveTime) {
this.cyclicBarrier = cyclicBarrier;
this.name = name;
this.arriveTime = arriveTime;
}
@Override
public void run() {
try {
//模拟达到需要花的时间
Thread.sleep(arriveTime * 1000L);
System.out.println(name +"到达集合点");
cyclicBarrier.await();
System.out.println(name +"开始旅行啦~~");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public class TourGuideTask implements Runnable{
@Override
public void run() {
System.out.println("****导游分发护照签证 开始****");
try {
//模拟发护照签证需要2秒
Thread.sleep(2000);
System.out.println("****导游分发护照签证 结束****");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestClient {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3,new TourGuideTask());
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//登哥最大牌,到的最晚
fixedThreadPool.execute(new TravelTask(cyclicBarrier,"哈登",5));
fixedThreadPool.execute(new TravelTask(cyclicBarrier,"保罗",3));
fixedThreadPool.execute(new TravelTask(cyclicBarrier,"戈登",1));
fixedThreadPool.shutdown();
}
}
例子3
public class Horse implements Runnable {
private static int counter = 0;
private final int id = counter++;
private int strides = 0;
private static Random rand = new Random(47);
private static CyclicBarrier barrier;
public Horse(CyclicBarrier b) { barrier = b; }
@Override
public void run() {
try {
while(!Thread.interrupted()) {
synchronized(this) {
//赛马每次随机跑几步
strides += rand.nextInt(3);
}
barrier.await();
}
} catch(Exception e) {
e.printStackTrace();
}
}
public String tracks() {
StringBuilder s = new StringBuilder();
for(int i = 0; i < getStrides(); i++) {
s.append("*");
}
s.append(id);
return s.toString();
}
public synchronized int getStrides() { return strides; }
@Override
public String toString() { return "Horse " + id + " "; }
}
public class HorseRace implements Runnable {
private static final int FINISH_LINE = 25;
private static List<Horse> horses = new ArrayList<>();
private static ExecutorService exec = Executors.newCachedThreadPool();
@Override
public void run() {
StringBuilder s = new StringBuilder();
//打印赛道边界
for(int i = 0; i < FINISH_LINE; i++) {
s.append("=");
}
System.out.println(s);
//打印赛马轨迹
for(Horse horse : horses) {
System.out.println(horse.tracks());
}
//判断是否结束
for(Horse horse : horses) {
if(horse.getStrides() >= FINISH_LINE) {
System.out.println(horse + "won!");
exec.shutdownNow();
return;
}
}
//休息指定时间再到下一轮
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch(InterruptedException e) {
System.out.println("barrier-action sleep interrupted");
}
}
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(7, new HorseRace());
for(int i = 0; i < 7; i++) {
Horse horse = new Horse(barrier);
horses.add(horse);
exec.execute(horse);
}
}
}
实现多次使用源码:
nextGeneration();1.判断最后一个线程等待完成, 唤醒所有阻塞的线程,2.重置下count(count 每来一个线程都会进行减1)和generation,以便于下次循环。
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//获取barrier当前的 “代”也就是当前循环
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// 每来一个线程调用await方法都会进行减1
int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
// new CyclicBarrier 传入 的barrierCommand, command.run()这个方法是同步的,如果耗时比较多的话,是否执行的时候需要考虑下是否异步来执行。
if (command != null)
command.run();
ranAction = true;
// 这个方法1. 唤醒所有阻塞的线程,2. 重置下count(count 每来一个线程都会进行减1)和generation,以便于下次循环。
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
// 进入if条件,说明是不带超时的await
if (!timed)
// 当前线程会释放掉lock,然后进入到trip条件队列的尾部,然后挂起自己,等待被唤醒。
trip.await();
else if (nanos > 0L)
//说明当前线程调用await方法时 是指定了 超时时间的!
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
//Node节点在 条件队列内 时 收到中断信号时 会抛出中断异常!
//g == generation 成立,说明当前代并没有变化。
//! g.broken 当前代如果没有被打破,那么当前线程就去打破,并且抛出异常..
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
//执行到else有几种情况?
//1.代发生了变化,这个时候就不需要抛出中断异常了,因为 代已经更新了,这里唤醒后就走正常逻辑了..只不过设置下 中断标记。
//2.代没有发生变化,但是代被打破了,此时也不用返回中断异常,执行到下面的时候会抛出 brokenBarrier异常。也记录下中断标记位。
Thread.currentThread().interrupt();
}
}
//唤醒后,执行到这里,有几种情况?
//1.正常情况,当前barrier开启了新的一代(trip.signalAll())
//2.当前Generation被打破,此时也会唤醒所有在trip上挂起的线程
//3.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
if (g.broken)
throw new BrokenBarrierException();
//唤醒后,执行到这里,有几种情况?
//1.正常情况,当前barrier开启了新的一代(trip.signalAll())
//2.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
if (g != generation)
return index;
//唤醒后,执行到这里,有几种情况?
//.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
Semaphore
Semaphore 是一个计数信号量,必须由获取它的线程释放。
常用于限制可以访问某些资源的线程数量。
//创建具有给定的许可数和非公平的公平设置的 Semaphore。
Semaphore(int permits)
//创建具有给定的许可数和给定的公平设置的 Semaphore。
Semaphore(int permits, boolean fair)
使用最基本的acquire方法和release方法就可以实现Semaphore最常见的功能。
public class StudySemaphore {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
//信号量,只允许 3个线程同时访问
Semaphore semaphore = new Semaphore(3);
for (int i=0;i<10;i++){
final long num = i;
executorService.submit(new Runnable() {
@Override
public void run() {
try {
//获取许可
semaphore.acquire();
//执行
System.out.println("Accessing: " + num);
Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长
//释放
semaphore.release();
System.out.println("Release..." + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
executorService.shutdown();
}
}