CountDownLatch和CyclicBarrier的使用和区别

在并发编程中,总会有各种各样的需求,根据需求去制定解决方案,才能让我们更好的理解,假如我们有以下两个需求:

  1. 有十个线程去执行各自的任务,任务可以分为两个部分,前半部分线程开启就可以执行,后半部分需要需要满足某个条件才能继续往下执行,如果条件暂不满足,那就等待,等到条件满足时就可以开始执行,如果条件满足,就不需要等待,直接往下执行,等十个任务全部做完时才可以在当前线程做其他的事情;

  2. 同样有十个线程去执行各自的任务,任务也是分为两个部分,现在要求等所有前半部分执行完后才能开始执行后半部分。

就这么两个需求,第一个可以用CountDownLatch来解决,第二个可以用CyclicBarrier来解决,下面看代码:

  • CountDownLatch

	public static void main(String[] args) {

		CountDownLatch start = new CountDownLatch(1);
		CountDownLatch end = new CountDownLatch(5);
		for (int i = 0; i < 5; i++) {
			new Thread(new Worker(start, end)).start();
		}
		
		try {
			//这里就是你所需要添加条件的地方,条件满足就执行下面的方法,这是阻塞的线程既可以再次开始了
			if(true) {
				start.countDown();
			}
			end.await();
			System.out.println("所有任务做完了,接下来你可以干自己想干的了");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		

		
	}

	public static class Worker implements Runnable{
		CountDownLatch start;
		CountDownLatch end;

		public Worker(CountDownLatch cLatch, CountDownLatch cLatch2) {
			super();
			this.start = cLatch;
			this.end = cLatch2;
		}

		@Override
		public void run() {
			try {
				System.out.println("执行你任务的前半部分  = "+Thread.currentThread().getName());
				start.await();
				System.out.println("执行你任务的后半部分  = "+Thread.currentThread().getName());
				end.countDown();
				end.countDown();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		
	}

输出结果:

执行你任务的前半部分  = Thread-0
执行你任务的前半部分  = Thread-2
执行你任务的前半部分  = Thread-1
执行你任务的前半部分  = Thread-3
执行你任务的后半部分  = Thread-1
执行你任务的后半部分  = Thread-3
执行你任务的前半部分  = Thread-4
执行你任务的后半部分  = Thread-4
执行你任务的后半部分  = Thread-2
执行你任务的后半部分  = Thread-0
所有任务做完了,接下来你可以干自己想干的了

CountDownLatch的构造函数需要传一个数,我们假定是count,这个数意味着可以调用countDown()多少次,我们可以这样去理解,没调用一次countDown(),count就减1,只要count不为0,那么调用它的await()方法时,就会阻塞当前线程,直到count减为0时,之前调用到await()方法的地方就不在阻塞了,直白一点就是,只要count为0,不管什么时候调用到的await()方法都不在阻塞线程了,反之则一直阻塞线程。

  • CyclicBarrier

    public static void main(String[] args)  {
		
		int num = 10;
        CyclicBarrier barrier = new CyclicBarrier(num, new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("前半部分任务执行完了,这里在执行点自己任务,执行完后就可以执行后半部分任务了!");
            }
        });
        List<Thread> list = new ArrayList<>();
        for (int i = 1; i <= num; i++) {
            Thread thread = new Thread(new CyclicBarrierWorker(i, barrier));
            list.add(thread);
            thread.start(); 
        }
        //这里是为了让子线程的任务先执行完在执行主线程的任务
        for (Thread thread : list) {
        	 try {
				thread.join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        }
        System.out.println("所有的后半部分任务都执行完了,你可以做自己的事了");
		
	}

    public static class CyclicBarrierWorker implements Runnable {
	    private int id;
	    private CyclicBarrier barrier;

	    public CyclicBarrierWorker(int id, final CyclicBarrier barrier) {
	        this.id = id;
	        this.barrier = barrier;
	    }

	    @Override
	    public void run() {
	        // TODO Auto-generated method stub
	        try {
	            System.out.println("前半部分任务 id = "+id);
	            barrier.await(); // 大家等待最后一个线程到达
	            System.out.println("后半部分任务 id = "+id);
	        } catch (InterruptedException | BrokenBarrierException e) {
	            // TODO Auto-generated catch block
	            e.printStackTrace();
	        }
	    }
	}

输出结果:

前半部分任务 id = 1
前半部分任务 id = 3
前半部分任务 id = 2
前半部分任务 id = 4
前半部分任务 id = 9
前半部分任务 id = 5
前半部分任务 id = 8
前半部分任务 id = 7
前半部分任务 id = 10
前半部分任务 id = 6
前半部分任务执行完了,这里在执行点自己任务,执行完后就可以执行后半部分任务了!
后半部分任务 id = 6
后半部分任务 id = 4
后半部分任务 id = 9
后半部分任务 id = 3
后半部分任务 id = 2
后半部分任务 id = 1
后半部分任务 id = 10
后半部分任务 id = 7
后半部分任务 id = 8
后半部分任务 id = 5
所有的后半部分任务都执行完了,你可以做自己的事了

CyclicBarrier的构造函数也是需要传入一个数,我们取为count,这也就是说总共有count个栅栏锁,每调用一次它的await()方法时,栅栏锁就少一个,但这时是会阻塞线程的,当栅栏锁减为0时,也就说没有锁了,这时线程就不在阻塞了,这样下来,上面的那两个需求是不是就特别的简单了,如果说是用我们自己定义的锁,那实现起来可就没有这么简单了,这两个例子算是比较简单了,如果你还想更加深入的了解,可以结合下源码,源码不多,方法数也不多,结合这两个例子是很容易理解的。

猜你喜欢

转载自blog.csdn.net/tangedegushi/article/details/82224439
今日推荐