CyclicBarrer,可循环使用的屏障,功能是让多个线程到达某个点时被阻塞,直到最后一个线程达到这个屏障便释放所有线程,和CountDownLatch的区别即在于线程释放后屏障是否可重用。
实例化:通过带参数的new CyclicBarrer(N)可实例化CyclicBarrier,N代表需要屏障拦截(阻塞)的线程数,也可以使用new CyclicBarrier(N,Runnable)的方式指定当所有阻塞的线程都到达屏障点后优先执行的任务barrierAction。
public CyclicBarrier(int parties) {……}
public CyclicBarrier(int parties, Runnable barrierAction) {……}
阻塞线程:通过调用await方法告诉当前线程已到达屏障,进入阻塞等待状态。也可以指定阻塞时间await(timeout,unit),防止阻塞时间过长,当阻塞超过指定时间,抛出TimeoutException
public int await() throws InterruptedException, BrokenBarrierException {……}
public int await(long timeout, TimeUnit unit) {……}
测试demo:以 三个线程计算任务为例,其中一个线程计算时间很长,于是调用await(time,unit)来指定等待时间
public class CyclicBarrierService {
static ExecutorService executorService = new ThreadPoolExecutor(3, 3, 30L, TimeUnit.SECONDS, new ArrayBlockingQueue(15));
static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
public static void statistic() throws Exception {
Future task1 = executorService.submit(new Callable() {
@Override
public Integer call() throws Exception {
System.out.println("我是任务一");
cyclicBarrier.await(1, TimeUnit.SECONDS);
return 1;
}
});
Future task2 = executorService.submit(new Callable() {
@Override
public Integer call() throws Exception {
System.out.println("我是任务二");
cyclicBarrier.await(1, TimeUnit.SECONDS);
return 2;
}
});
Future task3 = executorService.submit(new Callable() {
@Override
public Integer call() throws Exception {
System.out.println("我是任务三");
Thread.sleep(5000); //模拟任务3要执行很长时间
cyclicBarrier.await(1, TimeUnit.SECONDS);
return 3;
}
});
int result1 = task1.get();
int result2 = task2.get();
int result3 = task3.get();
System.out.println("多线程计算结果为");
System.out.println(result1 + result2 + result3);
executorService.shutdown();
}
public static void main(String[] args) throws Exception {
statistic();
}
}
因为我在获取线程计算结果时候未使用FutureTask.isDone()来判断当前任务是否计算完成(直接调用FutureTask.get()可能会阻塞,加了isDone判断,由于子线程任务还被阻塞在屏障点,所以获取不到计算结果),上述代码就抛出超时异常
我是任务一
我是任务二
我是任务三
Exception in thread "main" java.util.concurrent.ExecutionException: java.util.concurrent.TimeoutException
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:188)
at com.pptv.activityapi.controller.actmodule.CyclicBarrierService.statistic(CyclicBarrierService.java:49)
at com.pptv.activityapi.controller.actmodule.CyclicBarrierService.main(CyclicBarrierService.java:62)
Caused by: java.util.concurrent.TimeoutException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:427)
at com.pptv.activityapi.controller.actmodule.CyclicBarrierService$1.call(CyclicBarrierService.java:25)
at com.pptv.activityapi.controller.actmodule.CyclicBarrierService$1.call(CyclicBarrierService.java:21)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
重置计数器N:reset方法将屏障重置为其初始状态。 如果任何一方当前正在屏障等待,他们将返回BrokenBarrierException。
测试demo:
public class CyclicBarrierService {
static ExecutorService executorService = new ThreadPoolExecutor(3, 3, 30L, TimeUnit.SECONDS, new ArrayBlockingQueue(15));
static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
public static void statistic() {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
cyclicBarrier.await();
log.info("我是任务一……");
} catch (Exception e) {
e.printStackTrace();
}
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
try {
cyclicBarrier.await();
log.info("我是任务二……");
} catch (Exception e) {
e.printStackTrace();
}
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
try {
cyclicBarrier.reset();
log.info("我是任务三……");
} catch (Exception e) {
e.printStackTrace();
}
}
});
executorService.shutdown();
}
public static void main(String[] args) {
statistic();
}
}
第三个线程调用CyclicBarrier.reset()方法后将导致前面两个阻塞在屏障点的线程显式的抛出java.util.concurrent.BrokenBarrierException,输出结果如下
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:243)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:355)
at com.pptv.activityapi.controller.actmodule.CyclicBarrierService$1.run(CylicBarrierService.java:25)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:243)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:355)
at com.pptv.activityapi.controller.actmodule.CyclicBarrierService$2.run(CylicBarrierService.java:37)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
2018-11-14 15:11:39,535[com.pptv.activityapi.controller.actmodule.CyclicBarrierService][INFO]我是任务三……
结合上面说的,CyclicBarrier非常适合多线程计算任务,功能还是和CountDownLatch一致的,分组执行任务,最后汇总结果
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.*;
@Slf4j
@Service
public class CyclicBarrierService {
static ExecutorService executorService = new ThreadPoolExecutor(3, 3, 30L, TimeUnit.SECONDS, new ArrayBlockingQueue(15));
static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
public static void statistic() throws Exception {
Future task1 = executorService.submit(new Callable() {
@Override
public Integer call() throws Exception {
log.info("我是任务一");
cyclicBarrier.await(1, TimeUnit.SECONDS);
return 1;
}
});
Future task2 = executorService.submit(new Callable() {
@Override
public Integer call() throws Exception {
log.info("我是任务二");
cyclicBarrier.await(1, TimeUnit.SECONDS);
return 2;
}
});
Future task3 = executorService.submit(new Callable() {
@Override
public Integer call() throws Exception {
log.info("我是任务三");
cyclicBarrier.await(1, TimeUnit.SECONDS);
return 3;
}
});
int result1 = task1.get();
int result2 = task2.get();
int result3 = task3.get();
log.info("多线程计算结果为");
log.info(String.valueOf(result1 + result2 + result3));
executorService.shutdown();
}
public static void main(String[] args) throws Exception {
statistic();
}
}
输出结果为
2018-11-14 16:39:24,127[com.pptv.activityapi.controller.actmodule.CyclicBarrierService][INFO]我是任务一
2018-11-14 16:39:24,127[com.pptv.activityapi.controller.actmodule.CyclicBarrierService][INFO]我是任务二
2018-11-14 16:39:24,127[com.pptv.activityapi.controller.actmodule.CyclicBarrierService][INFO]我是任务三
2018-11-14 16:39:24,128[com.pptv.activityapi.controller.actmodule.CyclicBarrierService][INFO]多线程计算结果为
2018-11-14 16:39:24,128[com.pptv.activityapi.controller.actmodule.CyclicBarrierService][INFO]6
引申阅读:
Java中的线程池和异步任务详解
CountDownLatch(闭锁)的简单使用