CountDownLatch&CyclicBarrier&Semaphor
文章目录
引言
CountDownLatch&CyclicBarrier&Semaphor是JUC中重要的三个多线程控制工具,可用于处理特殊场景。
一、CountDownLatch
1、使用
- 主要用于控制一个线程或者多个线程等待某些线程完成任务的场景。
- 通过初始化计数项进行计数处理,当计数缩减为0,则会唤醒在这上面等待的所有线程。
public class CountDownLatchUsage {
public static void main(String[] args) {
CountDownLatch count=new CountDownLatch(2);
Runnable runnable= () -> {
System.out.println(1);
count.countDown();
};
Runnable runnable1= () -> {
System.out.println(2);
count.countDown();
};
new Thread(runnable).start();
new Thread(runnable1).start();
try {
count.await();
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName());
}
}
2、实现原理
它是基于AQS的一个实现,其内部实现非常简单,就是通过维护一个计数,同时这些等待的线程都是持有这个共享锁。
public class CountDownLatch {
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
//提供await调用,用于阻塞
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//提供countDown调用,用于释放
protected 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;
}
}
}
private final Sync sync;
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);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void countDown() {
sync.releaseShared(1);
}
public long getCount() {
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
二、CyclicBarrier
概念
- Generation
- 代,用于支持CylicBarrier的循环使用,就是代的更迭。
- Barrier
- 屏障,是一个临界点。
- trip
- 一个condition,是独占锁的condition,主要让线程进行等待。
- runCommand
- 优先执行任务,当满足屏障条件,先执行该任务,而后再唤醒其它线程。
1、使用
线程屏障,可以让多个线程阻塞,直到线程数量达到屏障规定的线程数,才开始执行。同时,能够设置优先执行任务,当到达屏障先执行优先任务,再执行线程各自的任务。
public class CyclicBarrierTest {
public static void main(String[] args) {
Runnable finalTask = () -> {
//do final task when all threads arrived the barrier.
System.out.println("END");
};
//final Task will action at first.
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, finalTask);
new Thread(() -> {
try {
cyclicBarrier.await();
System.out.println("1");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
cyclicBarrier.await();
System.out.println("2");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
cyclicBarrier.await();
System.out.println("3");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
//System.out.println();
}
}
2、实现原理
- 采用condition与reentrantLock作为底层的锁支持
/** The lock for guarding barrier entry */
//采用一个可重入锁
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
//底层是采用一个condition作为监视器
private final Condition trip = lock.newCondition();
/** The number of parties */
private final int parties;
/** The command to run when tripped */
//这是优先执行任务
private final Runnable barrierCommand;
- 重要实现,await
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();
}
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)
//出现异常情况,ranAction不能为true,手动打破屏障。
breakBarrier();
}
}
//一直处于loop
// 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();
}
}
以下是唤醒实现:
/**
* Updates state on barrier trip and wakes up everyone.
* Called only while holding lock.
*/
private void nextGeneration() {
// signal completion of last generation
//通知上面的所有等待线程,执行各自的任务。
trip.signalAll();
// set up next generation
count = parties;
//进入下一代
generation = new Generation();
}
/**
* Sets current barrier generation as broken and wakes up everyone.
* Called only while holding lock.
*/
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
分析:
- await这个过程,是获取独占锁的过程,获取锁之后,就会进行一个loop,持续等待,并且释放锁,提供给其它线程获取。
- 每一次的await,会使得当代计数器自减,直到当代计数器等于0,则执行优先命令,而后进行下一代,这个过程会唤醒所有的线程,执行他们自己的任务,其实也就是从等待队列中放到同步队列里面去。
三、Semaphor
java中还有一种控制多线程数量的方式,就是信号量。它可以用于控制并发数。
1、应用场景
- 流量控制,比如数据库连接。
- 假如数据库只开发了允许最大十个连接,而现在一个需求是读取几万个文件后并且存储到数据库,程序可能需要开启多个线程进行处理。当线程数超过数据库连接数就会出现错误。因此,可以用信号量作为控制
- 在微服务中被应用在hystrix中,用于控制并发量。
2、使用
public class SemaphorTest {
private static final int MAX_SMAPHOR = 10;
private static ExecutorService pool = Executors.newFixedThreadPool(300);
private static Semaphore semaphore = new Semaphore(MAX_SMAPHOR);
public static void main(String[] args) {
for (int i = 0; i < 300; i++) {
pool.execute(() -> {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(semaphore.hasQueuedThreads());
//if don't release the semaphor,all threads will wait.
//semaphore.release();
});
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(semaphore.hasQueuedThreads());
}
}
除了以上demo中的用法,还有下列:
- availablePermits
- 返回信号量中当前可用的许可证数
- getQueueLength
- 返回正在等待许可证的线程数
- hasQueuedThreads
- 是否有线程正在等待
- reducePermits
- 减少一定量的许可证。
- getQueueThreads
- 获取正在等待的线程集合
3、实现原理
- 先从构造信号量开始
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
可以看到,其实也是AQS,permits是许可证数。
- 调进许可证数的实现
Sync(int permits) {
//设置状态值
setState(permits);
}
看到这里,感觉它是一个共享锁的拓展。
- 查看是如何获取信号量许可证的
public void acquire() throws InterruptedException {
//注意是Shared,没错了,就是共享锁的拓展。
sync.acquireSharedInterruptibly(1);
}
public void release() {
sync.releaseShared(1);
}
- 查看是如何管理信号量的
//正常情况下的释放,这个release正常是1
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
//减少指定数量
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
总结
到这里,总结一下,能够发现,其实很多的实现,都是基于AQS,所以说AQS是JUC锁的重要核心与基础。
- CountDownLatch
- 用于让一个或者多个线程等待其它线程完成任务。
- CyclicBarrier
- 感觉像是众筹。
- Semaphor
- 信号量主要用于控制并发数量。