Java线程之CyclicBarrier

来源:互联网 发布:总经理助理与总监 知乎 编辑:程序博客网 时间:2024/05/29 19:11

    CountDownLatch和CyclicBarrier都能在线程执行时设置中间等待位置,表面上看相似度很高,下面讲讲他们的区别:

    CountDownLatch是线程A等待B。它有着明确的顺序,A先执行,到中间等待位置后,A调用countDown()方法,激活B,B才开始执行。再强调一下,这个类强调顺序,A先,B后

    CyclicBarrier是线程间互相等待,即A和B互相等待,可能是A等待B,也可能是B等待A。举个例子,当年纣王对苏妲己吟过一首诗:爱妃听我真心言,你我想约共百年,谁若九十七岁死,奈何桥上等三年。这种情况就必须使用CyclicBarrier来实现。中间等待位置是奈何桥上,纣王和苏妲己是2个线程,他们活在人世时是线程的前半部分,相约100岁时在奈何桥见面,然后牵手走完线程的后半部分。但人有旦夕祸福,两人都能活到100岁吗?不一定,可能纣王命短,97岁就死了,于是他到奈何桥头等待妲己。反之也有可能妲己命短,她97岁死了,去奈何桥等纣王3年。这就是所谓的相互等待,谁等谁都是有可能的。并且,两人在奈何桥会和后,线程并未结束,二人还会手牵手继续前行,走完线程的后半部分。

    CyclicBarrier还有一个重要特点,他是可重用的,当计数器减少到0开闸后,计数器会自动地恢复为初始值,以便下一次使用。

    CyclicBarrier应用举例:多线程实现希尔排序。以长度为10的数组{3,6,7,1,2,9,5,12,10,16}为例,开3个线程处理。

    当步长为5时,原数组被分为5个子数组,分别是:

3,                  9    6,                 5        7,                12            1,                10                2,                 16
    用3个线程对着5个子数组进行插入排序,第一个线程负责{3,9}和{1,10}两个子数组;第二个线程负责{6,5}和{2,16}两个子数组;第三个线程负责{7,12}这一个子数组。三个线程分开运行,互不干扰,无需加锁同步。使用CyclicBarrier让他们都在排序结束后相互等待。当3个线程全部排序完毕,修改步长为2,然后3个开工继续对子数组进行插入排序。这时有个问题,线程有3个,子数组只有2个,因此,第三个线程将什么都不做,直接执行await()语句进入等待状态。以此类推,直到步长为1排序结束后。整个数组排序结束,3个线程也自然结束。

    以下是源码,定义了一个MultiThreadAlgorithm类,有一个静态方法shellSort(),在该方法中创建多个线程,进行希尔排序。最后定义了线程对象,负责具体的排序操作。此代码使用了Java 7的语法

//多线程算法对象,内部定义了一些静态方法,均用多线程实现public class MultiThreadAlgorithm {public static void shellSort(int[] a, int threadCount) {List<Integer> gaps = new ArrayList<>();CyclicBarrier barrier = new CyclicBarrier(threadCount);CountDownLatch latch = new CountDownLatch(threadCount);int gap = a.length/2;while(gap>=1) {//希尔排序步长的选择是最基本的... 8,4,2,1gaps.add(gap);gap /= 2;}for(int i=0; i<threadCount; i++) {new ShellSortThread(i, threadCount, barrier, gaps, latch, a).start();}try {latch.await();//在多线程排序时,用此门闩将主线程阻塞,直到排序结束} catch (InterruptedException e) {e.printStackTrace();}}}//负责排序的线程class ShellSortThread extends Thread {private int threadID;//当前线程的编号,取值为 0, 1, 2, 3 ...private int threadSum;//开启的线程总数private CyclicBarrier barrier;//设置执行中间点private List<Integer> gaps;//希尔排序的步长序列private CountDownLatch latch;//用于阻塞主线程,与希尔排序算法无关private int[] a;//待排序的数组public ShellSortThread(int threadID, int threadSum, CyclicBarrier barrier,List<Integer> gaps, CountDownLatch latch, int[] a) {super();this.threadID = threadID;this.threadSum = threadSum;this.barrier = barrier;this.gaps = gaps;this.latch = latch;this.a = a;}/* * 算法思想:希尔排序原理是利用步长将原序列划分为多个子序列,对每个子序列进行插入排序 * 例如,若步长为8,则划分为8个子序列,并且这些子序列是互不相关的, * 因此,可以使用多线程来对这些子序列进行插入排序,不存在同步问题。 * 例如,开6个线程进行排序,当步长为20时, * 第1个线程负责第1,7,13,19个子序列的排序, * 第2个线程负责第2,8,14,20个子序列的排序, * ...... * 第6个线程负责第6,12,18个子序列的排序 * 每个线程使用当前步长排序结束后,调用CyclicBarrier对象的await()方法等待其他线程 * 当所有线程都使用当前步长排序结束后,CyclicBarrier闸门打开,更改步长,重复上面的步骤排序 * 当步长减少到1时,只有第1个线程进行插入排序,其他线程空转,直接await() * 最后,排序结束 */@Overridepublic void run() {for(int i=0, gap; i<gaps.size(); i++) {gap = gaps.get(i);for(int sub_array_index = threadID; sub_array_index<gap; sub_array_index += threadSum) {for(int j=sub_array_index+gap; j<a.length; j += gap) {int temp = a[j];int k;for(k=j-gap; k>=0 && a[k] > temp; k -= gap) {a[k+gap] = a[k];}a[k+gap] = temp;}}try {barrier.await();} catch (InterruptedException | BrokenBarrierException e) {//若发生异常,必须修改latch后退出,否则主线程会永远阻塞latch.countDown();return;}}latch.countDown();}}