今天我们就详细介绍一下JUC的一些常用同步工具类,减少计数(CountDownLatch),循环栅栏(CyclicBarrier),信号灯(Semaphore)的使用和区别。
CountDownLatch
作用:就是一个或者多个线程在开始执行操作之前,必须要等到其他线程执行完成才可以执行。
类的构造方法CountDownLatch(int count)
构造一个用给定计数初始化的值。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
核心方法
countDown()
递减锁存器的计数,如果计数达到零,将释放所有等待的线程
public void countDown() {
sync.releaseShared(1);
}
await()
使当前线程在锁存器倒计数至零之前一直处于等待。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
当线程调用await()方法时,就会阻塞当前线程。当线程调用一次countDown()方法时,count就会减一,直到当count 的值等于0时候,被阻塞的线程才可以继续执行。
现在我们用一个生活中的例子说明:学生时代,当我们在考试的时候,监考老师必须等到所有的学生交完卷子才可以离开,此时监考老师就相当于等待线程,而学生就好比是执行的线程。
我们用代码实现这个案例:
参加考试的学生10个, main线程就相当于监考老师
public class CountDownLatchDemo {
private static CountDownLatch countDownLatch = new CountDownLatch(10);
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"\t 学生交卷:");
// 减一
countDownLatch.countDown();
}
},String.valueOf(i)).start();
}
// 等待
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t 监考老师离开教室");
}
}
底层实现原理
从源码我们不难发现CountDownLatch是基于AQS实现的,当我们在构建CountDownLatch对象时,传入的值其实就会赋值给 AQS 的关键变量state,执行countDown()方法时,其实就是利用CAS 将state 减一,执行await()方法时,其实就是判断state是否为0,不为0则加入到队列中,将该线程阻塞掉(除了头节点),因为头节点会一直自旋等待state为0,当state为0时,头节点把剩余的在队列中阻塞的节点也一并唤醒。
CyclicBarrier
作用:N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
常用的构造方法有:CyclicBarrier(int parties,Runnable barrierAction)
创建一个新的CyclicBarrier,它将在给定数量的线程处于等待状态时启动,并在启动barrier时执行给定的屏障操作,该操作由最后一个进入barrier的线程操作
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
核心方法
await()
在所有的参与者都已经在此barrier上调用await方法之前一直等待
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
生活中的例子:在打王者的时候,在开局前所有人都必须要加载到100%才可以进入。否则所有玩家都相互等待。
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier =new CyclicBarrier(5, () -> System.out.println("游戏开始"));
for (int i = 1; i <=5 ; i++) {
int finalI = i;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("第"+ finalI +"进入游戏");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
},String.valueOf(i)).start();
}
}
}
总结:CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行 await()之后方法。
底层实现原理
从源码不难发现的是,它没有像CountDownLatch和ReentrantLock使用AQS的state变量,而CyclicBarrier是直接借助ReentrantLock加上Condition 等待唤醒的功能 进而实现的。
在构建CyclicBarrier时,传入的值会赋值给CyclicBarrier内部维护count变量,也会赋值给parties变量,每次调用await()方法时,会将count 减一 ,操作count值是直接使用ReentrantLock来保证线程安全性。如果count不为0,则添加Condition队列中,如果count等于0时,则把节点从Condition队列添加至AQS的队列中进行全部唤醒,并且将parties的值重新赋值为count的值。
Semaphore
信号量,用来控制同一时间,资源可被访问的线程数量,一般应用场景流量的控制。
构造方法Semaphore(int permits)
创建具有给定的许可数和非公平的公平设置的Semapore
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
核心方法acquire()
从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断,release()
释放一个许可,将其返回给信号量,设置许可数量Semaphore semaphore = new Semaphore(3)
,一般acquire()
都会抛出异常,release
在finally
中执行。
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void release() {
sync.releaseShared(1);
}
举例说明,6辆🚗强站三个停车位。
public class SemaphoreDemo {
public static void main(String[] args) {
// 模拟资源类,有3个空车位
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <=6 ; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"\t 抢到了车位");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
System.out.println(Thread.currentThread().getName()+"\t 离开了车位");
semaphore.release();
},String.valueOf(i)).start();
}
}
}
Semaphore 底层实现是基于AQS实现的类似于CountDownLatch
总结
CountDownLatch,Semaphore都是基于AQS实现。
CountDownLatch是一个线程等待其他线程,CyclicBarrier是线程之间相互等待。
CountDownLatch会将构造CountDownLatch的入参传递至state,countDown()就是在利用CAS将state减一,await()实际就是让头节点一直在等待state为0时,释放所有等待的线程。
CyclicBarrier则利用ReentrantLock和Condition,自身维护了count和parties变量。每次调用await将count减一,并将线程加入到Condition队列上。等到count为0时,则将Condition队列的节点移交至AQS队列,并全部释放。
文章链接:CountDownLatch,CyclicBarrier,Semaphore的使用及底层实现
👉 如果本文对你有帮助的话,欢迎点赞|关注,非常感谢
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。