CountDownLatch与CyclicBarrier详解

1. CountDownLatch

1.1 简介

CountDownLatch是一个同步辅助类,通过它可以完成类似于阻塞当前线程的功能,即:一个线程或多个线程一直等待,直到其他线程执行的操作完成。CountDownLatch用一个给定的计数器来初始化,该计数器的操作是原子操作,即同时只能有一个线程去操作该计数器。调用该类await方法的线程会一直处于阻塞状态,直到其他线程调用countDown方法使当前计数器的值变为零,每次调用countDown计数器的值减1。当计数器值减至零时,所有因调用await()方法而处于等待状态的线程就会继续往下执行。这种现象只会出现一次,因为计数器不能被重置,如果业务上需要一个可以重置计数次数的版本,可以考虑使用CycliBarrier。

1.2 API

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
用指定的值初始化计数器。

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

调用该方法的线程进入等待状态,直到计数器的值减至0或者该线程被其他线程Interrupted。

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

调用该方法的线程进入等待状态,直到计数器的值减至0或者该线程被其他线程Interrupted或者等待时间超过指定的时间。

    public void countDown() {
        sync.releaseShared(1);
    }

减少计数器 当前的值,每次调用值减少1。

    public long getCount() {
        return sync.getCount();
    }

获取计数器 当前的值

1.3 使用场景

在某些业务场景中,程序执行需要等待某个条件完成后才能继续执行后续的操作;典型的应用如并行计算,当某个处理的运算量很大时,可以将该运算任务拆分成多个子任务,等待所有的子任务都完成之后,父任务再拿到所有子任务的运算结果进行汇总。

1.4 实际案例

子任务类:
public class Task implements Runnable {

	private String taskCode;
	
	private AtomicInteger count;

	private CountDownLatch ready_latch;
	
	private CountDownLatch begin_latch;

	private CountDownLatch end_latch;

	public Task(String taskCode, CountDownLatch ready_latch, CountDownLatch begin_latch,
			CountDownLatch end_latch, AtomicInteger count) {
		super();
		this.taskCode = taskCode;
		this.begin_latch = begin_latch;
		this.end_latch = end_latch;
		this.ready_latch = ready_latch;
		this.count = count;
	}

	public void run() {
		try {
			Thread.sleep(1000);
			System.out.println("子线程:  任务" + taskCode + "准备就绪。。。");
			ready_latch.countDown();//已准备的任务+1
			begin_latch.await();//等待开始信号
			System.out.println("子线程:  任务" + taskCode + "开始执行。。。");
			count.addAndGet(Integer.valueOf(taskCode));
			Thread.sleep(1000);
			System.out.println("子线程:  任务" + taskCode + "执行完成。。。");
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally{
			end_latch.countDown();//已完成的任务+1
		}
	}
}

public class TestMain {

	public static void main(String[] args) throws InterruptedException {
		AtomicInteger count = new AtomicInteger(0);
		int taskNum = 10;
		CountDownLatch ready_latch = new CountDownLatch(taskNum);
		CountDownLatch begin_latch = new CountDownLatch(1);
		CountDownLatch end_latch = new CountDownLatch(taskNum);
		Executor executor = Executors.newCachedThreadPool();
		System.out.println("主线程--> 开始分发任务。。。");
		for(int i=1; i<=taskNum; i++){
			Task task = new Task(String.valueOf(i), ready_latch, begin_latch, end_latch, count);
			executor.execute(task);
		}
		System.out.println("主线程--> 等待所有子任务准备就绪。。。");
		ready_latch.await();
		System.out.println("主线程--> 所有子任务已准备就绪,通知子任务执行。。。");
		begin_latch.countDown();
		end_latch.await();
		System.out.println("主线程--> 所有子任务执行完毕,获得结果:"+count.get());
	}
}

执行结果:

主线程--> 开始分发任务。。。
主线程--> 等待所有子任务准备就绪。。。
子线程:  任务1准备就绪。。。
子线程:  任务3准备就绪。。。
子线程:  任务6准备就绪。。。
子线程:  任务7准备就绪。。。
子线程:  任务5准备就绪。。。
子线程:  任务9准备就绪。。。
子线程:  任务10准备就绪。。。
子线程:  任务8准备就绪。。。
子线程:  任务4准备就绪。。。
子线程:  任务2准备就绪。。。
主线程--> 所有子任务已准备就绪,通知子任务执行。。。
子线程:  任务1开始执行。。。
子线程:  任务9开始执行。。。
子线程:  任务4开始执行。。。
子线程:  任务5开始执行。。。
子线程:  任务7开始执行。。。
子线程:  任务6开始执行。。。
子线程:  任务3开始执行。。。
子线程:  任务2开始执行。。。
子线程:  任务8开始执行。。。
子线程:  任务10开始执行。。。
子线程:  任务10执行完成。。。
子线程:  任务7执行完成。。。
子线程:  任务2执行完成。。。
子线程:  任务5执行完成。。。
子线程:  任务6执行完成。。。
子线程:  任务3执行完成。。。
子线程:  任务8执行完成。。。
子线程:  任务1执行完成。。。
子线程:  任务4执行完成。。。
子线程:  任务9执行完成。。。
主线程--> 所有子任务执行完毕,获得结果:55

2. CyclicBarrier

2.1 简介

CyclicBarrier也是一个同步辅助类,它允许一组线程相互等待,直到到达某个公共屏障点(common barrier point)。通过它可以完成多个线程之间相互等待,只有当每个线程都准备就绪后,才能各自继续往下执行后面的操作。类似于CountDownLatch,它也是通过计数器来实现的。当某个线程调用await方法时,该线程进入等待状态,且计数器加1,当计数器的值达到设置的初始值时,所有因调用await进入等待状态的线程被唤醒,继续执行后续操作。因为CycliBarrier在释放等待线程后可以重用,所以称为循环barrier。CycliBarrier支持一个可选的Runnable,在计数器的值到达设定值后(但在释放所有线程之前),该Runnable运行一次,注,Runnable在每个屏障点只运行一个。

2.2 API

    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

用指定值和Runnable初始化CyclicBarrier

    public CyclicBarrier(int parties) {
        this(parties, null);
    }

用指定值初始化CyclicBarrier

    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen;
        }
    }

调用该方法的线程进入等待状态,并且计数器加1,直到调用该方法的线程数达到设置值后或该线程被其他Interrputed

    public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }

调用该方法的线程进入等待状态,并且计数器加1,直到调用该方法的线程数达到设置值后或该线程被其他Interrputed或者等待时间超过指定时间。

    public boolean isBroken() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return generation.broken;
        } finally {
            lock.unlock();
        }
    }

判断该Barrier是否处于broker状态

    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }

重置barrier进入初始状态。

2.3  使用场景

使用场景类似于CountDownLatch

2.4  与CountDownLatch的区别

  • CountDownLatch主要是实现了1个或N个线程需要等待其他线程完成某项操作之后才能继续往下执行操作,描述的是1个线程或N个线程等待其他线程的关系CyclicBarrier主要是实现了多个线程之间相互等待,直到所有的线程都满足了条件之后各自才能继续执行后续的操作,描述的多个线程内部相互等待的关系
  • CountDownLatch是一次性的,而CyclicBarrier则可以被重置而重复使用。

2.5   实际案例

public class Task implements Runnable{

	private String taskCode;
	
	private AtomicInteger count;
	
	private CyclicBarrier begin_cyclicBarrier;
	
	private CyclicBarrier end_cyclicBarrier; 
	
	public Task(String taskCode, AtomicInteger count,
			CyclicBarrier begin_cyclicBarrier, CyclicBarrier end_cyclicBarrier) {
		super();
		this.taskCode = taskCode;
		this.count = count;
		this.begin_cyclicBarrier = begin_cyclicBarrier;
		this.end_cyclicBarrier = end_cyclicBarrier;
	}



	@Override
	public void run() {
		try{
			System.out.println("子线程: 子任务"+taskCode+"已准备就绪,等待其他子任务就绪。。。");
			begin_cyclicBarrier.await();
			System.out.println("子线程:  子任务"+taskCode+"开始执行");
			Thread.sleep(1000);
			count.addAndGet(Integer.valueOf(taskCode));
			System.out.println("子线程:  子任务"+taskCode+"执行完成");
			end_cyclicBarrier.await();
		}catch(Exception e){
		}
	}
}

public class ParentTask implements Runnable {

	private String msg;
	
	public ParentTask(String msg) {
		super();
		this.msg = msg;
	}

	@Override
	public void run() {
		System.out.println(msg);
	}
}

public class TestMain {

	public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
		AtomicInteger count = new AtomicInteger(0);
		int taskNum = 10;
		CyclicBarrier begin_cBarrier = new CyclicBarrier(11, new ParentTask("主线程--> 所有子任务已准备就绪。。。"));
		CyclicBarrier end_cBarrier = new CyclicBarrier(11, new ParentTask("主线程--> 所有子任务已执行完成。。。"));
		Executor executor = Executors.newCachedThreadPool();
		System.out.println("主线程--> 开始分发任务。。。");
		for(int i=1; i<=taskNum; i++){
			Task task = new Task(String.valueOf(i), count, begin_cBarrier, end_cBarrier);
			executor.execute(task);
		}
		begin_cBarrier.await();
		end_cBarrier.await();
		System.out.println("主线程--> 所有子任务执行完毕,获得结果:"+count.get());
	}
}

执行结果:

主线程--> 开始分发任务。。。
子线程: 子任务1已准备就绪,等待其他子任务就绪。。。
子线程: 子任务2已准备就绪,等待其他子任务就绪。。。
子线程: 子任务3已准备就绪,等待其他子任务就绪。。。
子线程: 子任务4已准备就绪,等待其他子任务就绪。。。
子线程: 子任务6已准备就绪,等待其他子任务就绪。。。
子线程: 子任务7已准备就绪,等待其他子任务就绪。。。
子线程: 子任务5已准备就绪,等待其他子任务就绪。。。
子线程: 子任务10已准备就绪,等待其他子任务就绪。。。
子线程: 子任务8已准备就绪,等待其他子任务就绪。。。
子线程: 子任务9已准备就绪,等待其他子任务就绪。。。
主线程--> 所有子任务已准备就绪。。。
子线程:  子任务9开始执行
子线程:  子任务1开始执行
子线程:  子任务3开始执行
子线程:  子任务2开始执行
子线程:  子任务6开始执行
子线程:  子任务7开始执行
子线程:  子任务4开始执行
子线程:  子任务8开始执行
子线程:  子任务10开始执行
子线程:  子任务5开始执行
子线程:  子任务3执行完成
子线程:  子任务7执行完成
子线程:  子任务4执行完成
子线程:  子任务10执行完成
子线程:  子任务5执行完成
子线程:  子任务8执行完成
子线程:  子任务1执行完成
子线程:  子任务6执行完成
子线程:  子任务2执行完成
子线程:  子任务9执行完成
主线程--> 所有子任务已执行完成。。。
主线程--> 所有子任务执行完毕,获得结果:55


ok,至此,就简单的介绍和如何使用CountDownLatch与CyclicBarrier。

你可能感兴趣的:(多线程)