CountDownLatch 和 CyclicBarrier
一. CountDownLatch 的介绍
CountDownLatch 主要用于一个或多个线程等待其他线程完成操作。平常如果使用 new Thread()
开启多个线程,一般会使用 join()
来等待线程完成,但是如果开启的线程太多,或者是直接使用线程池里面的线程就不能再用这种方法,所以就使用 CountDownLatch 来对完成的线程计数,等待所有线程都完成,CountDownLatch 的计数器就为 0,所有任务就完成了。
1. CountDownLatch 的使用
CountDownLatch 使用方法就只需要实例一个对象,如果有 N 个线程,构造函数就传入 N 就行。然后每个线程运行过程的最后使用 countDown()
将计数减 1,最后使用 await()
等待计数为 0,就相当于所有线程都完成了。
public class ThreadTest {
private static final ReentrantLock lock = new ReentrantLock();
private static final CountDownLatch latch = new CountDownLatch(2);
private int num = 0;
public int getNum() {
return num;
}
private void count() {
lock.lock();
try {
for (int i = 0; i < 100000; i++) {
num+=1;
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ThreadTest lockTest = new ThreadTest();
Thread thread1 = new Thread(() -> {
lockTest.count();
latch.countDown();
});
Thread thread2 = new Thread(() -> {
lockTest.count();
latch.countDown();
});
thread1.start();
thread2.start();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(lockTest.getNum());
}
}
1. CountDownLatch 的原理
CountDownLatch 也是使用的队列同步器来计数,同步器的初始化会将线程数作为 state
变量的值,然后每次使用 countDown()
就减一,直到 state
为 0。
private static final 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;
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
二. CyclicBarrier 的介绍
CyclicBarrier 的字面意思是可循环的屏障,这边的可循环的意思是可以重复使用的,不像 CountDownLatch 只能使用一次。CyclicBarrier 相当于一个屏障,等到所有线程都到达屏障,这个屏障才会消失。
1. CyclicBarrier 的使用
CyclicBarrier 有两个构造函数,一个只需要传入拦截的线程数就行,另外一个是传入一个 Runnable 类型的参数,这个参数的主要作用是只要 CyclicBarrier 的计数器为 0,就会触发这个函数。
public class ThreadTest {
private static final CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("计数器为 0");
});
private void count() {
System.out.println(Thread.currentThread().getName() + " 抢占");
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 结束");
}
public static void main(String[] args) {
ThreadTest lockTest = new ThreadTest();
new Thread(() -> {
lockTest.count();
}, "线程1").start();
new Thread(() -> {
lockTest.count();
}, "线程2").start();
new Thread(() -> {
lockTest.count();
}, "线程3").start();
}
}
2. CyclicBarrier 的原理
CyclicBarrier 的 await()
方法的原理就是使用可重入锁来保证多个线程同时处理。同时使用 Generation 构建了一个屏障来保证所有线程到达。
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)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// 计算线程到达的数量,如果最终线程全部到达,就会运行传入的 Runnable 类型的函数
int index = --count;
if (index == 0) {
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
// 唤醒所有线程并新建一个屏障
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// 阻塞已经到达屏障的线程,直到所有线程到达再放开
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 {
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();
}
}
三. CountDownLatch 和 CyclicBarrier 的区别
- CountDownLatch 的计数器只能使用一次,而 CyclicBarrier 可以使用
reset()
方法重置 - CountDownLatch 主要是用来解决一个线程等待多个线程的场景,而 CyclicBarrier 是一个屏障拦截多个线程。