一、CountDownLatch
是一个同步辅助器,允许一个或多个线程一直等待,直到其他线程的操作全部完成。
常用方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void countDown() {
sync.releaseShared(1);
}
当一个线程调用await方法时,就会阻塞当前线程。每当有线程调用一次 countDown 方法时,计数就会减 1。当 count 的值等于 0 的时候,被阻塞的线程才会继续运行。
比如我们吃饭前 需要先做饭 洗手 再去吃饭 那么我们可以让 做吃饭这个任务的线程调用 await方法
这个线程就会一直阻塞到其他线程完成操作
public class CountDownTest {
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
Worker w1 = new Worker("张三", 2000, latch);
Worker w2 = new Worker("李四", 3000, latch);
w1.start();
w2.start();
long startTime = System.currentTimeMillis();
latch.await();
System.out.println("bug全部解决,领导可以给客户交差了,任务总耗时:"+ (System.currentTimeMillis() - startTime));
}
static class Worker extends Thread{
String name;
int workTime;
CountDownLatch latch;
public Worker(String name, int workTime, CountDownLatch latch) {
this.name = name;
this.workTime = workTime;
this.latch = latch;
}
@Override
public void run() {
System.out.println(name+"开始修复bug,当前时间:"+sdf.format(new Date()));
doWork();
System.out.println(name+"结束修复bug,当前时间:"+sdf.format(new Date()));
latch.countDown();
}
private void doWork() {
try {
//模拟工作耗时
Thread.sleep(workTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
CyclicBarrier
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;
}
第一个构造的参数,指的是需要几个线程一起到达,才可以使所有线程取消等待。第二个构造,额外指定了一个参数,用于在所有线程达到屏障时,优先执行 barrierAction。
barrier 英文是屏障,障碍,栅栏的意思。cyclic是循环的意思,就是说,这个屏障可以循环使用
一组线程会互相等待,直到所有线程都到达一个同步点。这个就非常有意思了,就像一群人被困到了一个栅栏前面,只有等最后一个人到达之后,他们才可以合力把栅栏(屏障)突破。
现在模拟一个常用的场景,一组运动员比赛 1000 米,只有在所有人都准备完成之后,才可以一起开跑(额,先忽略裁判吹口哨的细节)。
定义一个 Runner 类代表运动员,其内部维护一个共有的 CyclicBarrier,每个人都有一个准备时间,准备完成之后,会调用 await 方法,等待其他运动员。当所有人准备都 OK 时,就可以开跑了。
public class BarrierTest {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3); //①
Runner runner1 = new Runner(barrier, "张三");
Runner runner2 = new Runner(barrier, "李四");
Runner runner3 = new Runner(barrier, "王五");
ExecutorService service = Executors.newFixedThreadPool(3);
service.execute(runner1);
service.execute(runner2);
service.execute(runner3);
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();
}
}
}
再设想一个场景需要 所有人都准备完成了 然后等待裁判发号施令
这里就要用到第二个构造函数了,于是代码 ① 处稍微修改一下。
CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
@Override
public void run() {
try {
System.out.println("等裁判吹口哨...");
//这里停顿两秒更便于观察线程执行的先后顺序
Thread.sleep(2000);
System.out.println("裁判吹口哨->>>>>");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
刚才,提到了循环利用是怎么体现的呢。我现在把屏障值改为 2,然后增加一个“赵六” 一起参与赛跑。被修改的部分如下:
三、Semaphore
Semaphore 信号量,用来控制同一时间,资源可被访问的线程数量,一般可用于流量的控制。
打个比方,现在有一段公路交通比较拥堵,那怎么办呢。此时,就需要警察叔叔出面,限制车的流量。
比如,现在有 20 辆车要通过这个地段, 警察叔叔规定同一时间,最多只能通过 5 辆车,其他车辆只能等待。只有拿到许可的车辆可通过,等车辆通过之后,再归还许可,然后把它发给等待的车辆,获得许可的车辆再通行,依次类推。
public class SemaphoreTest {
private static int count = 20;
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(count);
//指定最多只能有五个线程同时执行
Semaphore semaphore = new Semaphore(5);
Random random = new Random();
for (int i = 0; i < count; i++) {
final int no = i;
executorService.execute(new Runnable() {
@Override
public void run() {
try {
//获得许可
semaphore.acquire();
System.out.println(no +":号车可通行");
//模拟车辆通行耗时
Thread.sleep(random.nextInt(2000));
//释放许可
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
executorService.shutdown();
}
}
打印结果我就不写了,需要读者自行观察,就会发现,第一批是五个车同时通行。然后,后边的车才可以依次通行,但是同时通行的车辆不能超过 5 辆。
细心的读者,就会发现,这许可一共就发 5 个,那等第一批车辆用完释放之后, 第二批的时候应该发给谁呢?
这确实是一个问题。所有等待的车辆都想先拿到许可,先通行,怎么办。这就需要,用到锁了。就所有人都去抢,谁先抢到,谁就先走呗。
我们去看一下 Semaphore的构造函数,就会发现,可以传入一个 boolean 值的参数,控制抢锁是否是公平的。
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
默认是非公平,可以传入 true 来使用公平锁。(锁的机制是通过AQS,现在不细讲,等我以后更新哦)
这里简单的说一下,什么是公平非公平吧。
公平的话,就是你车来了,就按照先来后到的顺序正常走就行了。不公平的,也许就是,某位司机大哥膀大腰圆,手戴名表,脖子带粗金项链。别人一看惹不起,我还躲不起吗,都给这位大哥让道。你就算车走在人家前面了,你也不敢跟人家抢啊。
最后,那就是他先拿到许可证通行了。剩下的人再去抢,说不定又来一个,是警察叔叔的私交好友。(行吧行吧,其他司机只能一脸的生无可恋,怎么过个马路都这么费劲啊。。。)
总结
-
CountDownLatch 是一个线程等待其他线程, CyclicBarrier 是多个线程互相等待。
-
CountDownLatch 的计数是减 1 直到 0,CyclicBarrier 是加 1,直到指定值。
-
CountDownLatch 是一次性的, CyclicBarrier 可以循环利用。
-
CyclicBarrier 可以在最后一个线程达到屏障之前,选择先执行一个操作。
-
Semaphore ,需要拿到许可才能执行,并可以选择公平和非公平模式。