CountDownLatch、CyclicBarrier和 Semaphore
简介
CountDownLatch、CyclicBarrier和 Semaphore是J.U.C中的三个辅助类,大致功能如下:
CountDownLatch可以实现线程A等待其他多个线程执行完后再继续执行。
CyclicBarrier可以实现一组线程等待至某个状态后再全部同时执行。
Semaphore可以控同时访问的线程个数,比如3个资源有5个线程请求,则只有3个线程能得到。
借助上面三个辅助类能完成很多并发的问题。详细介绍之前,需要了解下volatile关键字、CAS操作和AQS,这里只是简单介绍下。
volatile关键字
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。也就是保证变量的值是最新的。
CAS
CAS(Compare and Swap)是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试(自旋,使用返回的最新值再次计算进行CAS操作)。
AQS
AbstractQueuedSynchronizer抽象队列同步器,是用来构建锁或其他同步组件的基础框架。它使用了一个int成员变量表示同步状态,通过内置FIFO队列来完成资源获取线程的排队工作。AQS提供三个(原子式)方法(getState()、setState(int newState)、compareAndSetState(int expect,intupdate)[使用CAS(CompareAndSwap)设置当前状态,保证方法的原子性])来保证状态的改变是安全的。
CountDownLatch
java.util.concurrent包下的一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
例子
public class test_CountDownLatch { public static void main(String[] args){ final CountDownLatch latch = new CountDownLatch(3); for (int i=0;i<3;i++){ new Thread(new worker(latch)).start(); } try { System.out.println("等待子线程执行完毕..."); latch.await(); System.out.println("3个子线程已经执行完毕"); System.out.println("继续执行主线程"); } catch (InterruptedException e) { e.printStackTrace(); } } static class worker implements Runnable{ private final CountDownLatch latch; public worker(CountDownLatch latch){ this.latch=latch; } @Override public void run() { try { System.out.println(Thread.currentThread().getName()+"正在工作"); Thread.sleep(2048); System.out.println(Thread.currentThread().getName()+"工作完毕"); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
输出:从结果可以看出0,1,2是并行执行,然后main线程等待所有结束后才继续执行。
等待子线程执行完毕...
Thread-1正在工作
Thread-0正在工作
Thread-2正在工作
Thread-0工作完毕
Thread-1工作完毕
Thread-2工作完毕
3个子线程已经执行完毕
继续执行主线程
源码
静态内部类
静态内部类的同步器Sync,继承AQS,就是控制并发环境下对线程数量的原子性操作。
/**
* Synchronization control ForCountDownLatch.
* Uses AQS state to represent count.
*/
private staticfinal class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {//设置状态,被等待的线程数
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;//如果线程数为0,则返回1
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transitionto zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))//CAS操作
return nextc == 0;
}
}
}
构造函数
私有成员privatefinal Sync sync,也就是上面的同步器。构造函数传入一个count,表示需要等待count个线程完成后才能继续执行。
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count);//静态内部类中构造函数 }
方法
l void await() //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行,除非被中断。这里sync.acquireSharedInterruptibly(1);里面调用了tryAcquireShared(1),从上面的源码来看return (getState() == 0) ? 1 : -1;也就是线程数(状态)为0时才返回1。如果还有线程未结束,则调用AQS中的doAcquireSharedInterruptibly(intarg)方法,进入自旋,也就是等待状态变为0才能继续运行。
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
l boolean await(longtimeout, TimeUnit unit) 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行。
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); }
l void countDown() 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
public void countDown() {sync.releaseShared(1);}
l long getCount()返回当前计数。这个没啥好说的。
public long getCount() {return sync.getCount();}
l String toString()返回标识此锁存器及其状态的字符串。重写toString方法,也没啥好说的。
public String toString() {return super.toString() + "[Count = " + sync.getCount() + "]";}
CyclicBarrier
java.util.concurrent包下一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点(common barrierpoint)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环的barrier。
例子
public class test_CyclicBarrier { public static void main(String[] args) { int N = 4; CyclicBarrier barrier = new CyclicBarrier(N); for(int i=0;i<N;i++) new Writer(barrier).start(); } static class Writer extends Thread{ private CyclicBarrier cyclicBarrier; Writer(CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; } @Override public void run() { System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据..."); try { Thread.sleep((long) (Math.random()*5000)); //以睡眠来模拟写入数据操作 System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕"); cyclicBarrier.await();//所有线程都达到Barrier再继续执行后续代码 } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } System.out.println("所有线程写入完毕"+Thread.currentThread().getName()+"继续处理其他任务..."); } } }
输出:从结果可以看出写完的顺序有快有慢,但当所有都写完后,所有线程才开始“同时”处理后续任务。
线程Thread-0正在写入数据...
线程Thread-1正在写入数据...
线程Thread-2正在写入数据...
线程Thread-3正在写入数据...
线程Thread-1写入数据完毕,等待其他线程写入完毕
线程Thread-0写入数据完毕,等待其他线程写入完毕
线程Thread-2写入数据完毕,等待其他线程写入完毕
线程Thread-3写入数据完毕,等待其他线程写入完毕
所有线程写入完毕Thread-3继续处理其他任务...
所有线程写入完毕Thread-1继续处理其他任务...
所有线程写入完毕Thread-2继续处理其他任务...
所有线程写入完毕Thread-0继续处理其他任务...
源码
几个私有成员
/**CyclicBarrier可以多次使用,每次均表示为一代生成的实例。每当Barrier触发时会重置(=false)或者产生新一代(new Generation())*/ private static class Generation {boolean broken = false;}//只有一个boolean变量 /** 防护屏障入口锁 */ private final ReentrantLock lock = new ReentrantLock(); /** 等待,直到trip的条件 */ private final Condition trip = lock.newCondition(); /** 加入线程的数量 */ private final int parties; /* 当达到trip条件时候运行的Runnable线程(如果设置有的话) */ private final Runnable barrierCommand; /** 当前的一代 */ private Generation generation = new Generation(); /**仍在运行未达到trip条件的线程数(从parties到0)*/ private int count;
构造函数,有两个
CyclicBarrier(intparties)
创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作,传入的Runnable是null。
public CyclicBarrier(int parties) { this(parties, null); }
CyclicBarrier(intparties, Runnable barrierAction)
创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。这个说法其实有歧义,看private int dowait这个方法的源码可以知道,是最后一个完成到达barrier的线程执行,但是执行的内容是传入的Runnable对象的run()方法,注意这里不是新启动了一个线程,而是直接调用的run()方法。
public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction; }
几个私有方法
/**进入下一代*/ private void nextGeneration() { // 唤醒上一代的每个等待trip的线程 trip.signalAll(); // 设置下一代,初始化count数量,new Generation count = parties; generation = new Generation(); }
/**不生成下一代,将broken设置为true,唤醒所有等待trip的线程*/ private void breakBarrier() { generation.broken = true; count = parties; trip.signalAll(); }
/**主要的逻辑流程代码都在这*/ private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock; lock.lock();//上锁 try { final Generation g = generation; if (g.broken)//如果调用了breakBarrier的话,这个会抛异常 throw new BrokenBarrierException(); if (Thread.interrupted()) {//线程中断会调用breakBarrier方法 breakBarrier(); throw new InterruptedException(); } int index = --count; if (index == 0) { //所有线程都执行到Barrier,只有最后一个线程会进入这段代码 boolean ranAction = false; try { final Runnable command = barrierCommand; if (command != null) command.run();//调用的是run()方法,没启动新线程,是最后一个线程 ranAction = true; nextGeneration();//重置CyclicBarrier,可以再次使用 return 0; } finally { if (!ranAction) breakBarrier(); } } // loop until tripped, broken, interrupted, or timed out for (;;) { try { if (!timed) trip.await();//到达Barrier的线程等待其他线程到达 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(); } }
方法
l int await() 在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。调用的是上面的dowait()方法。
public int await() throws InterruptedException, BrokenBarrierException { try { return dowait(false, 0L); } catch (TimeoutException toe) { throw new Error(toe); // cannot happen } }
l int await(long timeout,TimeUnit unit) 在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。和上面类似,传入了时间单元。
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException { return dowait(true, unit.toNanos(timeout)); }
l int getNumberWaiting() 返回当前在屏障处等待的参与者数目。
public int getNumberWaiting() { final ReentrantLock lock = this.lock; lock.lock(); try { return parties - count; } finally { lock.unlock(); } }
l int getParties() 返回要求启动此 barrier 的参与者数目。这个没啥好说的。
public int getParties() {return parties;}
l boolean isBroken() 查询此屏障是否处于损坏状态。
public boolean isBroken() { final ReentrantLock lock = this.lock; lock.lock(); try { return generation.broken; } finally { lock.unlock(); } }
l void reset() 将屏障重置为其初始状态。
public void reset() { final ReentrantLock lock = this.lock; lock.lock(); try { breakBarrier(); // break the current generation nextGeneration(); // start a new generation } finally { lock.unlock(); } }
Semaphore
java.util.concurrent包下一个同步辅助类,代表计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,对数据库连接池的访问。
例子
public class test_Semaphore { public static void main(String[] args) { int N = 4; //读数据库线程数 Semaphore semaphore = new Semaphore(2); //数据库连接数 for(int i=0;i<N;i++) new Worker(i,semaphore).start(); } static class Worker extends Thread{ private int num; private Semaphore semaphore; public Worker(int num,Semaphore semaphore){ this.num = num; this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println("线程"+this.num+"占用一个数据库连接在读取..."); Thread.sleep((long) (Math.random()*2000)); semaphore.release(); System.out.println("线程"+this.num+"释放数据库连接"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
输出:
线程0占用一个数据库连接在读取...
线程1占用一个数据库连接在读取...
线程1释放数据库连接
线程3占用一个数据库连接在读取...
线程0释放数据库连接
线程2占用一个数据库连接在读取...
线程2释放数据库连接
线程3释放数据库连接
源码
静态内部类
静态内部类的同步器Sync,继承AQS。同时还包括NonfairSync和FairSync两个非公平和公平的同步器,继承Sync。这里就不贴代码了,理解公平锁和非公平锁原理比较简单。
构造函数
有两个,给定的许可数和是否公平同步器,默认非公平。
public Semaphore(int permits) { sync = new NonfairSync(permits); }
设置公平的同步器的Semaphore。
public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }
常用方法
该类的方法较多,这里列举几个常用方法。
l void acquire()从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
l void acquire(int permits)从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。
l void release()释放一个许可,将其返回给信号量。
l void release(int permits) 释放给定数目的许可,将其返回到信号量。
acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。release()用来释放许可。注意,在释放许可之前,必须先获。这4个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法:
l boolean tryAcquire()仅在调用时此信号量存在一个可用许可,才从信号量获取许可。
l boolean tryAcquire(int permits)仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。
l boolean tryAcquire(int permits, long timeout, TimeUnit unit) 如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。
l boolean tryAcquire(long timeout, TimeUnit unit)如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可。
总结
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
Semaphor一般用于控制对某组资源的访问权限。