Java线程之CyclicBarrier

    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,1
			gaps.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()
	 * 最后,排序结束
	 */
	@Override
	public 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();
	}	
}


你可能感兴趣的:(java,java,java,java,算法,线程,Cyclicbarrier,希尔排序)