JUC 辅助类
CountDownLatch
它是一个同步辅助器,允许一个或多个线程一直等待,直到一组在其他线程执行的操作全部完成;
它的构造方法,会传入一个 count 值,用于计数;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
常用的方法有如下两个;
当一个线程调用 await 方法时,就会阻塞当前线程。每当有线程调用一次 countDown 方法时,计数就会减 1,当 count 的值等于 0 的时候,被阻塞的线程才会继续运行;
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void countDown() {
sync.releaseShared(1);
}
示例
当所有人离开教室后才允许关门;
public class CountDownTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 离开教室");
latch.countDown();
},i + "").start();
}
latch.await();
System.out.println("教室中所有人已离开,可以锁门");
}
}
CyclicBarrier
一组线程会互相等待,直到所有线程都到达一个同步点;
就像一群人被困到了一个栅栏前面,只有等最后一个人到达之后,他们才可以合力把栅栏(屏障)突破
CyclicBarrier 提供了两种构造方法,如下
第一个构造的参数,指的是需要几个线程一起到达,才可以使所有线程取消等待;
第二个构造,额外指定了一个参数,用于在所有线程达到屏障时,优先执行 barrierAction;
主要使用的方法就是 await 方法;并且 CyclicBarrier 可以循环利用;
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
示例
现在模拟一个常用的场景,一组运动员比赛 1000 米,只有在所有人都准备完成之后,然后裁判吹口哨之后才可以一起开跑;
public class BarrierTest {
public static void main(String[] args) {
//当所有线程到达一个同步点时,就会优先执行该匿名实现类的方法
CyclicBarrier cyclicBarrier = new CyclicBarrier(4, () -> {
System.out.println("各就各位,开始!!!");
});
Runner r1 = new Runner(cyclicBarrier, "张三");
Runner r2 = new Runner(cyclicBarrier, "李四");
Runner r3 = new Runner(cyclicBarrier, "王五");
Runner r4 = new Runner(cyclicBarrier, "赵六");
ExecutorService service = Executors.newFixedThreadPool(4);
service.execute(r1);
service.execute(r2);
service.execute(r3);
service.execute(r4);
service.shutdown();
}
}
class Runner implements Runnable {
private CyclicBarrier barrier;
private String name;
public Runner(CyclicBarrier barrier, String name) {
this.barrier = barrier;
this.name = name;
}
@Override
public void run() {
try {
//模拟准备耗时
Thread.sleep(new Random().nextInt(5000));
System.out.println(name + ":准备OK");
//当前线程进入等待
barrier.await();
System.out.println(name +": 开跑");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e){
e.printStackTrace();
}
}
}
结果:
张三:准备OK
赵六:准备OK
李四:准备OK
王五:准备OK
各就各位,开始!!!
王五: 开跑
张三: 开跑
赵六: 开跑
李四: 开跑
CountDownLatch 和 CyclicBarrier 区别
- CountDownLatch 类似是一个计数器,线程完成一个记录一个,计数器递减,只能使用一次;
- CyclicBarrier 的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供 reset 功能,可以多次使用;
Semaphore
Semaphore 信号量,用来控制同一时间,资源可被访问的线程数量,一般可用于流量的控制;
在信号量上,有定义两种操作,并且 Semaphore 可以控制抢锁是否是公平;
- acquire(获取)当前一个线程调用 acquire 操作时,它要么通过成功获取信号量(信号量减 1 ),要么一直等待下去,直到有线程释放信号量或超时;
- release(释放)实际上会将信号量的值加 1 ,然后唤醒等待的线程;
public class SemaphoreTest {
public static void main(String[] args) {
//公平的抢锁
Semaphore semaphore = new Semaphore(3,true);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
//当前下称到达后,获取一个信号量,也就是 信号量-1
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "\t 号车已经抢到车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t 号车离开该车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//当前线程执行完毕,那么再次将信号量释放,也即是 信号量+1
semaphore.release();
}
},"" + i).start();
}
}
}
总结
- CountDownLatch 是一个线程等待其他线程, CyclicBarrier 是多个线程互相等待;
- CountDownLatch 的计数是减 1 直到 0,CyclicBarrier 是加 1,直到指定值;
- CountDownLatch 是一次性的, CyclicBarrier 可以循环利用;
- CyclicBarrier 可以在最后一个线程达到屏障之前,选择先执行一个操作;
- Semaphore ,需要拿到许可才能执行,并可以选择公平和非公平模式;