CyclicBarrier
在现实生活中会有这样的场景:在开始某项活动之前必须等待所有人员都到位才可以开始。例如,在篮球比赛中,必须等待5名球员上场后才能开始比赛。又如一个旅游团必须所有人到达同一个景点参观后,才能一起到下一个景点继续游玩。那么如果叫你使用多线程来实现这些场景,你会怎么实现呢?
- 在Java.util.concurrent包中,为我们提供了一个并发工具类CyclicBarrier,CyclicBarrier也叫循环栅栏,是一个可循环利用的屏障。通过它可以实现让一组线程等待至某个状态之后再全部同时执行。每个线程在到达栅栏的时候都会调用await()方法将自己阻塞,此时计数器会减1,当计数器减为0的时候所有因调用await方法而被阻塞的线程将被唤醒。叫做循环是因为当所有等待线程都被释放以后,CyclicBarrier还可以被重用(调用CyclicBarrier的reset()方法)。
CyclicBarrier使用方式(例子)
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("集齐七张卡片,参与抽奖");
});
for (int i = 1; i <= 7; i++) {
final int temp=i;
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"收集到第"+temp+"张");
//阻塞任务线程
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
程序输出:
Thread-1收集到第2张
Thread-4收集到第5张
Thread-3收集到第4张
Thread-2收集到第3张
Thread-0收集到第1张
Thread-6收集到第7张
Thread-5收集到第6张
集齐七张卡片,参与抽奖
CyclicBarrier的常用方法
-
int await()阻塞调用该方法的线程,等待其他线程到达屏障点 -
int await(long timeout, TimeUnit unit)指定被阻塞线程的等待时间。 -
int getNumberWaiting()返回已到达屏障的线程数量。 -
int - getParties()返回CyclicBarrier屏障所需的线程数量,即需要多少个线程到达屏障点,所有被阻塞线程才能继续向下执行。 -
boolean isBroken()查询这个障碍是否处于损坏状态。 -
void reset()将屏障重置为初始状态。
原理
CyclicBarrier还是基于AQS实现的,内部维护parties记录总线程数,count用于计数,最开始count=parties,调用await()之后count原子递减,当count为0之后,再次将parties赋值给count,这就是复用的原理。
- 当子线程调用await()方法时,获取独占锁,同时对count递减,进入阻塞队列,然后释放锁
- 当第一个线程被阻塞同时释放锁之后,其他子线程竞争获取锁,操作同
- 直到最后count为0,执行CyclicBarrier构造函数中的任务,执行完毕之后子线程继续向下执行。
CountDownLatch
CountDownLatch也是在java.util.concurrent包下的另一个并发工具类,利用它可以实现类似倒计数器的功能,CountDownLatch可以等待多个线程执行完毕后再做一件事情。比如有一个任务 A,它要等待其他 4 个任务执行完毕之后才能执行,此时就可以利用 CountDownLatch来实现这种功能了。CountDownLatch提供了一个countDown()方法来操作计数器的值,每调用一次countDown()方法计数器就会减1,直到计数器的值减为0时,因调用CountDownLatch的await()方法而阻塞的线程都会被唤醒。
CountDownLatch使用方式(例子)
public static void main(String[] args) throws InterruptedException {
//初始计数器数量
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
//在 Lambda表达式中是无法拿到for循环中的i变量的,可以声明一个final中间变量来获取
final int temp=i;
new Thread(()->{
//计数器-1
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+":"+" 执行完成 :"+temp);
}).start();
}
//阻塞,直到子线程全部执行完毕,假设计数器变为0,countDownLatch.await()就会被唤醒,继续执行
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"全部任务执行完成");
}
程序输出:
Thread-0: 执行完成 :1
Thread-3: 执行完成 :4
Thread-2: 执行完成 :3
Thread-1: 执行完成 :2
Thread-4: 执行完成 :5
Thread-5: 执行完成 :6
main全部任务执行完成
🚨CountDownLatch也可以用于实现使多个线程同时执行,如指定初始化计数器值为1,在多个线程中(即子任务)中调用await()方法进行阻塞,当所有线程都调用了await()方法被阻塞后,主线程调用countDown()使计数器变为0,之前被阻塞的多个子任务就会被唤醒,这时多个子任务就会同时继续往下执行。
CyclicBarrier与CountDownLatch的区别
-
CountDownLatch的await()方法阻塞的是主线程或调用await()的线程,而CyclicBarrier的await()阻塞的是任务线程,主线程或调用线程不受影响。
-
CountDownLatch无法重置计数次数,而CyclicBarrier可以通过reset()方法来重用
-
CountDownLatch和CyclicBarrier都是用作多线程同步,都是基于AQS实现。
原理
CountDownLatch基于AQS实现,volatile变量state维持倒数状态,多线程共享变量可见。
- CountDownLatch通过构造函数初始化传入参数实际为AQS的state变量赋值,维持计数器倒数状态。
- 当主线程调用await()方法时,当前线程会被阻塞,当state不为0时进入AQS阻塞队列等待。
- 其他线程调用countDown()时,state值原子性递减,当state值为0的时候,唤醒所有调用await()方法阻塞的线程。
Semaphore
Semaphore也叫信号量,是一种用来控制同时访问共享资源的线程数量的并发工具。Semaphore通过协调各个线程,以保证各个线程合理地使用共享资源。比如可以把Semaphore比作我们小区的车库,车库的停车位数量是有限的,只允许同时停放3辆车,在这里3个停车位就是共享资源,如果车库中已停满3辆车,那么当有第4量车想要停进车库时,就必须等待,直到3辆车中有一辆驶离车库时,那么就允许后面一辆车停入车库。
Semaphore使用方式(例子)
public static void main(String[] args) {
//可用资源数目,可以用来限制线程数量,多个共享资源互斥使用,例如车位
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try { semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
//停2秒 ,即使睡眠也不会释放许可
TimeUnit.MILLISECONDS.sleep(2);
// System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}, "线程"+String.valueOf(i) ).start();
}
}
输出结果:
线程1抢到车位
线程3抢到车位
线程2抢到车位
线程2离开车位
线程1离开车位
线程4抢到车位
线程3离开车位
线程6抢到车位
线程5抢到车位
线程5离开车位
线程6离开车位
线程4离开车位
Semaphore常用方法
-
void acquire()许可数-1,从该信号量获取许可证,阻塞直到可用或线程被中断。 -
void acquire(int permits)许可数 - permits,从该信号量获取给定数量的许可证,阻塞直到可用或线程被中断。 -
int availablePermits()返回此信号量中当前可用的许可数。 -
void release()许可数+1,释放许可证,将其返回到信号量。 -
void release(int permits)许可数+permits,释放给定数量的许可证,将其返回到信号量。 -
boolean hasQueuedThreads() 查询是否有线程正在等待获取许可。 -
int getQueueLength()返回等待获取许可的线程数的估计。
总结
CyclicBarrier可以实现让一组线程等待至某个状态之后再全部同时执行,在这个过程中阻塞的是多个任务线程;CountDownLatch可以实现一个线程等待其他多个线程执行完毕后再继续执行,阻塞的是主线程或者调用线程;Semaphore信号量可以实现控制同时访问共享资源的线程数量,保证各个线程合理地使用共享资源。
以上就是对CyclicBarrier、CountLatch、Semaphore三个并发工具的简单描述,如果有错误的地方,还请留言指正,如果觉得本文对你有帮助那就点个赞吧
︿( ̄︶ ̄)︿