简介
上一篇文章介绍了CountDownLatch可以用来计数,另一个也可以实现此功能的就是CyclicBarrier,而且与CountDownLatch相比最大的优点就是可以重复使用,下面通过几种情况来介绍CyclicBarrier的使用。
计数等待
@Slf4j
public class Test5 {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(2);
new Thread(() -> {
try {
log.info("t1 执行中...");
Thread.sleep(1000);
barrier.await();
log.info("t1 执行结束");
} catch (Exception e) {
throw new RuntimeException(e);
}
}, "t1").start();
new Thread(() -> {
try {
log.info("t2 执行中...");
Thread.sleep(2000);
barrier.await();
log.info("t2 执行结束");
} catch (Exception e) {
throw new RuntimeException(e);
}
}, "t2").start();
}
}
上面创建了一个CyclicBarrier对象,并且初始化计数为2。创建两个子线程t1、t2执行任务并调用await方法进行等待,让t1睡眠1秒,t2睡眠2秒。根据最后输出结果可以看到t1、t2都是2秒后才继续执行的,也就是说当t1调用barrier.await()方法之后就开始阻塞等待,直到计数变为0之后才会继续执行。
使用CountDownLatch时,子线程调用countDown方法之后会立即返回,继续向下执行,不会等待其他线程。
结束处理
@Slf4j
public class Test5 {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(2, () -> log.info("子线程执行结束了"));
new Thread(() -> {
try {
log.info("t1 执行中...");
Thread.sleep(1000);
barrier.await();
log.info("t1 执行结束");
} catch (Exception e) {
throw new RuntimeException(e);
}
}, "t1").start();
new Thread(() -> {
try {
log.info("t2 执行中...");
Thread.sleep(2000);
barrier.await();
log.info("t2 执行结束");
} catch (Exception e) {
throw new RuntimeException(e);
}
}, "t2").start();
}
}
CyclicBarrier的构造方法中可以传入一个Runnable任务,当计数变为0之后就会执行这个任务,而且要优先于被唤醒的子线程继续执行,这点和CountDownLatch的使用就很相似了。
循环使用
@Slf4j
public class Test5 {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(2, () -> log.info("子线程执行结束了"));
ExecutorService service = Executors.newFixedThreadPool(2);
for (int i = 0; i < 3; i++) {
service.submit(() -> {
try {
log.info("线程t1执行中");
barrier.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
service.submit(() -> {
try {
log.info("线程t2执行中");
barrier.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
service.shutdown();
}
}
上面创建了一个最大线程数为
2的线程池,然后循环三次每次提交两个任务,根据输出结果可以看出一共执行了三次,也就是子线程把计数变为0之后又重新执行了任务,这是因为CyclicBarrier的计数变成0之后会重新把它修改成初始值,这里就是2,所以第二次循环时子线程又可以调用await方法来把计数减1,这样一直循环使用,这也是CyclicBarrier的最大特点。
超时等待
@Slf4j
public class Test5 {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(2, () -> log.info("子线程执行结束了"));
Thread t1 = new Thread(() -> {
try {
log.info("线程t1执行中");
barrier.await(1, TimeUnit.SECONDS);
log.info("线程t1执行结束");
} catch (Exception e) {
e.printStackTrace();
}
});
t1.start();
Thread t2 = new Thread(() -> {
try {
log.info("线程t2执行中");
Thread.sleep(3000);
barrier.await();
log.info("线程t2执行结束");
} catch (Exception e) {
e.printStackTrace();
}
});
t2.start();
}
}
让线程
t1调用await时只等待1秒,让t2先睡眠3秒再调用await方法。输出结果中可以看到t1线程在1秒后抛出了TimeoutException异常,而t2线程在2秒后抛出了BrokenBarrierException异常。当有一个线程破坏了CyclicBarrier的计数规则之后,其他线程也将不再遵守规则来等待计数变为0,而是直接抛出BrokenBarrierException异常。