[沧海拾遗]java并发之CountDownLatch、Semaphore和CyclicBarrier

本文介绍了Java并发编程中重要的三个工具类:CountDownLatch、Semaphore和CyclicBarrier。CountDownLatch用于阻塞线程直到计数器归零;Semaphore作为流量阀门,限制并发线程数量,支持释放许可;CyclicBarrier则充当关卡,所有线程到达关卡后统一执行后续操作。这三个工具常用于线程同步和流量控制。

JAVA并发包中有三个类用于同步一批线程的行为,分别是CountDownLatch、Semaphore和CyclicBarrier。

CountDownLatch

CountDownLatch是一个计数器闭锁,主要的功能就是通过await()方法来阻塞住当前线程,然后等待计数器减少到0了,再唤起这些线程继续执行。 这个类里主要有两个方法,一个是向下减计数器的方法:countdown(),其实现的核心代码如下:

public boolean tryReleaseShared(int releases) {  
	// Decrement count; signal when transition to zero  
	for (;;) {   
	int c = getState();     
	if (c == 0)    
		return false;     
	int nextc = c-1;    
	if (compareAndSetState(c, nextc))    
		return nextc == 0;    
	}    
}    

很简单,如果取得当前的状态为0,说明这个锁已经结束,直接返回false;如果没有结束,然后去设置计数器减1,如果compareAndSetState不成功,则继续循环执行。 而其中的一直等待计数器归零的方法是await()。 
通过CountDownLatch可以做几件事情:

1. 主线程控制同时启动一组线程

final CountDownLatch count = new CountDownLatch(1);
for (int i = 0; i < 3; i++) {
    new Thread("Thread" + i) {
        public void run() {
            System.out.println(Thread.currentThread().getName() + " wait");
            try {
                count.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " start");
        }
    }.start();
}
//等等三秒,否则有可能3个线程并没有全部进行await状态
try {
    Thread.sleep(3000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
count.countDown();

2. 主线程等待各子线程全部执行完毕后再往下执行:

final CountDownLatch count = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    new Thread("Thread" + i) {
        public void run() {
            System.out.println(Thread.currentThread().getName() + " start");
            count.countDown();
        }
    }.start();
}
try {
    count.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("All end!!!");    

Semaphore

Semaphore与CountDownLatch相似,不同的地方在于Semaphore的值被获取到后是可以释放的,并不像CountDownLatch那样一直减到底。它也被更多地用来限制流量,类似阀门的 功能。如果限定某些资源最多有N个线程可以访问,那么超过N个主不允许再有线程来访问,同时当现有线程结束后,就会释放,然后允许新的线程进来。有点类似于锁的lock与 unlock过程。相对来说他也有两个主要的方法:

  1. 用于获取权限的acquire(),其底层实现与CountDownLatch.countdown()类似;
  2. 用于释放权限的release(),其底层实现与acquire()是一个互逆的过程。

用Semaphore来实现限流代码详见:semaphore例子

CyclicBarrier

CyclicBarrier是用来一个关卡来阻挡住所有线程,等所有线程全部执行到关卡处时,再统一执行下一步操作,它里面最重要的方法是await()方法,其实现如下:

private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    //取锁,以防止在后面做减1计数时线程不安全
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        final Generation g = generation;

        if (g.broken)
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
       //如果当前线程执行到了,则将计数器减1,计数器为0则说明所有线程均执行到这里,可以调用下一步操作
       int index = --count;
       if (index == 0) {  // tripped
           boolean ranAction = false;
           try {
               //获取到定义好的下一步操作,并执行
	   final Runnable command = barrierCommand;
               if (command != null)
                   command.run();
               ranAction = true;
               nextGeneration();
               return 0;
           } finally {
               if (!ranAction)
                   breakBarrier();
           }
       }

        // loop until tripped, broken, interrupted, or timed out
        for (;;) {
            try {
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                if (g == generation && ! g.broken) {
                    breakBarrier();
		throw ie;
	    } else {
		// We're about to finish waiting even if we had not
		// been interrupted, so this interrupt is deemed to
		// "belong" to subsequent execution.
		Thread.currentThread().interrupt();
	    }
            }

            if (g.broken)
                throw new BrokenBarrierException();

            if (g != generation)
                return index;

            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}    

即每个线程执行完后调用await(),然后在await()里,线程先将计数器减1,如果计数器为0,则执行定义好的操作,然后再继续执行原线程的内容。 
这个类比之前两个类的一个好处是有点类似于切面编程,可以让我们在同类线程的某个切面切入一块逻辑,并且可以同步所有的线程的执行速度。
例子代码如下:

final CyclicBarrier barrier = new CyclicBarrier(4, new Runnable() {

    @Override
    public void run() {
        System.out.println("All Threads Here");

    }
});
for (int i = 0; i < 4; i++) {
    new Thread("Thread" + i) {
        public void run() {
            System.out.println(Thread.currentThread().getName() + " wait");
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " crossed");
        }
    }.start();
}  

最终的输出结果为:

Thread0 wait
Thread1 wait 
Thread2 wait 
Thread3 wait 
All Threads Here 
Thread0 crossed 
Thread1 crossed 
Thread2 crossed 
Thread3 crossed

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值