给大家推荐个靠谱的公众号程序员探索之路,大家一起加油
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 使用场景
在某些业务场景中,程序执行需要等待某个条件完成后才能继续执行后续的操作;典型的应用如并行计算,当某个处理的运算量很大时,可以将该运算任务拆分成多个子任务,等待所有的子任务都完成之后,父任务再拿到所有子任务的运算结果进行汇总。
public static void main(String[] args) throws InterruptedException {
Runnable taskTemp = new Runnable() {
private int num = 0;
@Override
public void run() {
for (int i = 0;i < 10;i++){
HttpUtil.getURLContent("https://www.baidu.com/");
num++;
System.out.println(System.nanoTime() + " [" + Thread.currentThread().getName() + "] iCounter = " + num);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// Thread t = new Thread(){
// public void run(){
// taskTemp.run();
// }
// };
// t.start();
Test test = new Test();
test.startTaskAllInOnce(5, taskTemp);
// test.startNThreadsByBarrier(5, taskTemp);
}
public long startTaskAllInOnce(int threadNums, final Runnable task) throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(threadNums);
for(int i = 0; i < threadNums; i++) {
Thread t = new Thread() {
public void run() {
try {
// 使线程在此等待,当开始门打开时,一起涌入门中
startGate.await();
try {
task.run();
} finally {
// 将结束门减1,减到0时,就可以开启结束门了
// endGate.countDown();
}
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
};
t.start();
}
long startTime = System.nanoTime();
System.out.println(startTime + " [" + Thread.currentThread() + "] All thread is ready, concurrent going...");
// 因开启门只需一个开关,所以立马就开启开始门
startGate.countDown();
// 等等结束门开启
endGate.await();
long endTime = System.nanoTime();
System.out.println(endTime + " [" + Thread.currentThread() + "] All thread is completed.");
return endTime - startTime;
}
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则可以被重置而重复使用。
public static void main(String[] args) throws InterruptedException {
Runnable taskTemp = new Runnable() {
private int num = 0;
@Override
public void run() {
for (int i = 0;i < 10;i++){
HttpUtil.getURLContent("https://www.baidu.com/");
num++;
System.out.println(System.nanoTime() + " [" + Thread.currentThread().getName() + "] iCounter = " + num);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// Thread t = new Thread(){
// public void run(){
// taskTemp.run();
// }
// };
// t.start();
Test test = new Test();
// test.startTaskAllInOnce(5, taskTemp);
test.startNThreadsByBarrier(5, taskTemp);
}
public void startNThreadsByBarrier(int threadNums, Runnable finishTask) throws InterruptedException {
// 设置栅栏解除时的动作,比如初始化某些值 注意 这里的finishTask执行的 时间是 该CyclicBarrier达到阙伐值后的回调的一个任务
CyclicBarrier barrier = new CyclicBarrier(threadNums, finishTask);
// 启动 n 个线程,与栅栏阀值一致,即当线程准备数达到要求时,栅栏刚好开启,从而达到统一控制效果
for (int i = 0; i < threadNums; i++) {
// Thread.sleep(100);
new Thread(new CounterTask(barrier)).start();
}
System.out.println(Thread.currentThread().getName() + " out over...");
}
class CounterTask implements Runnable {
private CyclicBarrier barrier;
public CounterTask(final CyclicBarrier barrier) {
this.barrier = barrier;
}
public void run() {
System.out.println(Thread.currentThread().getName() + " - " + System.currentTimeMillis() + " is ready...");
try {
// 设置栅栏,使在此等待,到达位置的线程达到要求即可开启大门
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " - " + System.currentTimeMillis() + " started...");
}
}