CyclicBarrier 是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。CyclicBarrier支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。
业务需求
一个大型任务常常分配好多子任务去执行,只有当所有的子任务都执行完成时,才能执行主任务。比如在考试系统的抽题逻辑中,抽题是一个大型任务,包括抽出各种题型,比如:单选题、多选题、翻译题、听力题。抽完所有的题才能继续向下执行。按照咱们以前写代码的方式:先抽取单选题,再抽多选题,再抽翻译题,再抽听力题,假设每个抽题时间都为1s,那么完成一个抽题需要4s的时间,显然对电脑资源的利用率没有达到最优。那么我们如何提高性能呢?假设我们是4核的cpu,我们在抽单选题的时候可以同时抽多选题、翻译题和听力题,这样相当于是并行执行多个任务,那么抽完题需要的时间才是1s。
实现
有了上面的需求,那么下面我们来实现,如何抽题。
抽单选题类:
/**
* 单选
*
* @author 康立贤
*
* 2015年5月19日2015
*/
public class SingleSelect implements Runnable {
private Resutl result;
private CyclicBarrier barrier;
private int number;
public SingleSelect(Resutl result,CyclicBarrier barrier,int number) {
this.result = result;
this.barrier=barrier;
this.number=number;
}
public SingleSelect(Resutl result,int number){
this.result = result;
this.number=number;
}
@Override
public void run() {
//System.out.println("单选==第"+number+"次==抽题===开始。。。");
result.setSingleSelect("单选题。。。"+number);
try {
// 睡5s
// Thread.sleep((int) (Math.random() * 1000));
System.out.println("单选==第"+number+"次==抽题===结束。。。");
barrier.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void singleExam(){
//System.out.println("单选==第"+number+"次==抽题===开始。。。");
result.setSingleSelect("单选题。。。"+number);
System.out.println("单选==第"+number+"次==抽题===结束。。。");
}
}
抽多选、翻译、听力的类与上面的类似,在此省略。
存放抽题结果的类---Result.java
/**
* 存放抽取到的试题
* @author 康立贤
*
* 2015年5月19日2015
*/
public class Resutl {
private String singleSelect;
private String doubleSelect;
private String translate;
private String listening;
public String getSingleSelect() {
return singleSelect;
}
public void setSingleSelect(String singleSelect) {
this.singleSelect = singleSelect;
}
public String getDoubleSelect() {
return doubleSelect;
}
public void setDoubleSelect(String doubleSelect) {
this.doubleSelect = doubleSelect;
}
public String getTranslate() {
return translate;
}
public void setTranslate(String translate) {
this.translate = translate;
}
public String getListening() {
return listening;
}
public void setListening(String listening) {
this.listening = listening;
}
}
合并抽题结果类:---Combine.java
public class Combine implements Runnable{
private Resutl result;
public Combine(Resutl result){
this.result=result;
}
@Override
public synchronized void run() {
//synchronized(this){
System.out.println("所有抽题全部完毕");
System.out.println(result.getDoubleSelect());
System.out.println(result.getSingleSelect());
System.out.println(result.getTranslate());
System.out.println(result.getListening());
}
}
客户端:
public class Exam3Pool {
//目前性能比较好的方式
public static void main(String[] args) {
// 获取服务器的cpu个数
int cpuNum = Runtime.getRuntime().availableProcessors();
System.out.println("cpu的个数是:" + cpuNum);
// 合适的线程数量是cpu数量
ExecutorService pool = Executors.newFixedThreadPool(cpuNum);
for(int i=0;i<200000;i++){
Resutl result=new Resutl();
Combine combine=new Combine(result);
CyclicBarrier barrier=new CyclicBarrier(4,combine);
DoubleSelect doubleSelect=new DoubleSelect(result,barrier,i);
Linstening listening=new Linstening(result,barrier,i);
SingleSelect singleSelect=new SingleSelect(result,barrier,i);
Translate translate=new Translate(result,barrier,i);
//多选抽题
Thread thDouble=new Thread(doubleSelect);
//听力抽题
Thread thListen=new Thread(listening);
//单选抽题
Thread thSingle=new Thread(singleSelect);
//翻译抽题
Thread thTranslate=new Thread(translate);
pool.execute(thDouble);
pool.execute(thListen);
pool.execute(thSingle);
pool.execute(thTranslate);
}
pool.shutdown();
System.out.println("主线程结束.........");
//20s
//21个线程
}
}
总结:
通过设置公共屏障点的方式,我们可以使线程同步,同步完成后执行下面的功能,比如说执行完一个任务需要5个子任务,使用此种方式是只要有一个子任务没有完成,那么其余任务线程必须等待,不能再执行。我们考虑一下,如果一个子任务用的时间很长,那么整个任务执行完毕的时间取决于执行最长时间的子任务,其他子任务线程完全等待,这样资源并没有得到很好的利用,那么能不能在等待其他子任务完成之前让完成了任务的子线程去执行其他任务呢?这样可以使资源更充分的被利用?答案肯定是有的,下篇博客我们会解决这个问题,敬请期待~~~