由一个笑话引发的一些思考:
据说是一个月薪 9K 的 Java 程序员,因老板让他写一个排序算法,然后他就写了一段屌炸天的休眠排序算法,接着他就被老板开除了……算法长这样:
突然想到的问题时,由于你的线程是一个一个开的,它们开始执行的时间不一样,如果数字都是很近的话,排出来的顺序可能都是不一致的。然后突然就想到了CyclicBarrier……然后就想知道下它里面是怎么保证多个线程在同一个起点开始跑的。
CyclicBarrier做的事情是,让一组线程到达一个屏障(也可以叫同步点-Barrier)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。而之所以叫Cyclic,是因为所有等待线程被释放后,CyclicBarrier可以被重用。
CyclicBarrier常用于线程组内部想成等待的问题(例如多线程分组计算,猜测fork/join里面也用到了)。
以我编写的代码为例,进行一次分析,代码如下:
public class useCyclicBarrier {
private static final AtomicInteger value = new AtomicInteger(0);
private static final CyclicBarrier c = new CyclicBarrier(4, new Runnable() {
@Override
public void run() {
System.out.println("now all Thread reach the same point");
}
});
public static void main(String[] args) {
test1();
}
/**
* @Description: 理解它的循环
* @param:
* @return:
* @author:
* @Date: 2018/11/14
*/
private static void test1() {
ThreadPoolExecutor pool = new ThreadPoolExecutor(4, 10,
60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
for (int i=0;i<4;i++){
pool.submit(new GoThread());
}
pool.shutdown();
}
private static class GoThread implements Runnable {
@Override
public void run() {
int threadName = value.incrementAndGet();
System.out.println("start execute Thread " + threadName + " " + System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(threadName);//睡眠不同的时间
System.out.println("start sleep");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
c.await();//拦住4条线程
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("end at the same time" + System.currentTimeMillis());//然后再同一时间结束
}
}
}
1.首先,在IDEA中让4条线程同时阻塞在await()方法这里(带有参数的await方法暂时不分析)。然后让第一个线程继续执行下去。
2.接着第一条线程会对当前代码加锁,这样是为了保证线程安全性。
其中generation但是用于判断当前的Barrier是否是有效的,如果当前的Barrier被broken了,那么之后的线程都会检测到这一信息,然后越过当前Barrier,并抛出一个异常。
如果当前线程被interrupt过,那么由当前线程充当broken Barrier的角色。
总之就是如果Barrier失效了,所有的线程都会越过这个Barrier。
3.接着第一条线程会执行—count,意思就是说,等待的线程又达到了一个。如果此时count不为0,说明还有线程未达到Barrier,那么就调用trip.await()进入阻塞。
trip.await()是AQS类中的一个方法,它会释放当前锁持有的锁,然后进入阻塞状态。释放锁的目的是让第二个线程可以进行—count的操作。
4.然后第二条和第三条线程来了,做了和第一条线程一样的操作,也成功阻塞了……
5.现在是最后一条线程来了,注意此时的count此时已经变成0了,也就是说所有线程都到达了Barrier点。
接着就执行构造函数中设置的任务,调用它的run()方法,run()就不用新开线程了,流弊流弊。
然后调用nextGeneration(),就是实现它的Cyclic功能,看下它里面是做了哪些操作:
该方法内部会唤醒所有阻塞的线程,是的它们可以继续执行。并且会重置下count和generation,真正的实现Cyclic,等待下一次的调用。
唤醒完所有线程之后,当前线程就可以return了,所有线程也就可以继续执行了,至此,我们简单的分析它的功能就结束了。
回到初始点,其实好像也并没有解决一起开始的问题,因为接下来还是走的线程调度的方法,一开始的那个排序算法差不多也是同一时间执行的,只是start线程需要点时间而已,不管怎么说,总算了解了CyclicBarrier的内部原理,也是蛮有收货的。