写在前面:
1,CountDownLatch
CountDownLatch计数器闭锁是一个能阻塞主线程,让其他线程满足特定条件下主线程再继续执行的线程同步工具。
Latch闭锁的意思,是一种同步的工具类。类似于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭着的,不允许任何线程通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。且当门打开了,就永远保持打开状态。
CountDowmLatch是一种灵活的闭锁实现,包含一个计数器,该计算器初始化为一个正数,表示需要等待事件的数量。countDown方法递减计数器,表示有一个事件发生,而await方法等待计数器到达0,表示所有需要等待的事情都已经完成。
用法:
用给定的计数初始化CountDownLatch,其含义是要被等待执行完的线程个数。
每次调用CountDown(),计数减1
主程序执行到await()函数会阻塞等待线程的执行,直到计数为0
常见案例:
1:多线程读取批量文件, 并且读取完成之后汇总处理
2:多线程读取Excel多个sheet,读取完成之后获取汇总获取的结果
3:多个人一起一起来吃饭,主人等待客人到来,客人一个个从不同地方来到饭店,主人需要等到所有人都到来之后,才能开饭
4:汽车站,所有乘客都从不同的地方赶到汽车站,必须等到所有乘客都到了,汽车才会出发,如果设置了超时等待,那么当某个时间点到了,汽车也出发
2,Semaphore
Semaphore是一个计数信号量,常用于限制可以访问某些资源(物理或逻辑的)线程数目。
jdk解释中的含义大概下面五点:
- Semaphore是一个计数信号量。
- 从概念上将,Semaphore包含一组许可证。
- 如果有需要的话,每个acquire()方法都会阻塞,直到获取一个可用的许可证。
- 每个release()方法都会释放持有许可证的线程,并且归还Semaphore一个可用的许可证。
- 然而,实际上并没有真实的许可证对象供线程使用,Semaphore只是对可用的数量进行管理维护。
3,CyclicBarrier
CyclicBarrier是java 5中引入的线程安全的组件叫循环屏障。它有一个barrier的概念,主要用来等待所有的线程都执行完毕,然后再去执行特定的操作。
它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过CyclicBarrier的await()方法。
假如我们有很多个线程,每个线程都计算出了一些数据,然后我们需要等待所有的线程都执行完毕,再把各个线程计算出来的数据加起来,的到最终的结果,那么我们就可以使用CyclicBarrier。
一,CountDownLatch
1.1,实战使用
项目需求:
三人跑步,有的人快,有的人慢,最后一个人跑完才算结束。
看代码:
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
Thread runner1 = new Mythread("豪子", latch);
Thread runner2 = new Mythread("李子", latch);
Thread runner3 = new Mythread("刚子", latch);
System.out.println("开始比赛。。。");
runner1.start();
runner2.start();
runner3.start();
latch.await();
System.out.println("比赛结束。。。");
}
}
class Mythread extends Thread{
private String runner;
private CountDownLatch latch;
public Mythread(String runner, CountDownLatch latch){
this.runner = runner;
this.latch = latch;
}
@Override
public void run() {
System.out.println(runner + "begin run");
try {
Thread.sleep(new Random().nextInt(4)*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(runner + "end run");
latch.countDown();
}
}
运行结果:
开始比赛。。。
豪子begin run
李子begin run
刚子begin run
刚子end run
李子end run
豪子end run
比赛结束。。。
到这里,不知道你理解CountDownLatch的用法没。
1.2,原理
AQS模型,共享模式锁。CAS操作state变量,等于0 表示无锁,即等待所有的锁结束。
这里,我们直接看三个api就好了。
①new CountDownLatch(3)
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
private volatile int state;
Sync(int count) {
setState(count);
}
可以看到,这里面设置volatile 类型的state变量为3。表示目前有桑格锁占用该对象。
②latch.countDown();
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
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;
}
}
对state变量进行减一操作。表示共享模式锁少一个持有者。
③,latch.await();
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
循环等待释放所有锁。
二,Semaphore
2.1,实战
项目需求:
五个人三条跑道,一条跑道每次只能跑一个人,一个人跑完,下个人才可以入场。
代码如下:
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(3);//非公平模式,即不是按照先来后到的顺序假如跑道
CountDownLatch latch = new CountDownLatch(5);
Thread runner1 = new MySemaPhorethread("李子", semaphore,latch);
Thread runner2 = new MySemaPhorethread("刚子", semaphore,latch);
Thread runner3 = new MySemaPhorethread("豪子", semaphore,latch);
Thread runner4 = new MySemaPhorethread("明子", semaphore,latch);
Thread runner5 = new MySemaPhorethread("丸子", semaphore,latch);
System.out.println("开始比赛。。。");
runner1.start();
runner2.start();
runner3.start();
runner4.start();
runner5.start();
latch.await();
System.out.println("比赛结束。。。");
}
}
class MySemaPhorethread extends Thread{
private String runner;
private Semaphore semaphore;
private CountDownLatch latch;
public MySemaPhorethread(String runner, Semaphore semaphore,CountDownLatch latch){
this.runner = runner;
this.semaphore = semaphore;
this.latch = latch;
}
@Override
public void run() {
try {
semaphore.acquire(); // 等待获取跑到,准备跑步
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(runner + "begin run");
try {
Thread.sleep(new Random().nextInt(4)*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(runner + "end run");
semaphore.release();// 跑完了,释放跑到
latch.countDown();
}
}
运行结果如下:
李子begin run
明子begin run
豪子begin run
李子end run
丸子begin run
豪子end run
刚子begin run
明子end run
刚子end run
丸子end run
比赛结束。。。
可以看到由于跑道资源只有三个,最多只有三个人 begin run.。当有一个人 end run的时候,才会有新的人加入跑到。这里面使用了 CountDownLatch来限制,5个人跑完才比赛结束。
2.2,原理
我们也是看三个api.
①,new Semaphore(3); //限制了三个资源,三个信号变量
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
两个构造函数,可以声明公平模式/非公平模式的信号变量。其底层还是AQS模型的共享节点state。
protected final void setState(int newState) {
state = newState;
}
②,semaphore.acquire(); //等待获取跑道,准备跑步
当前线程尝试去阻塞的获取1个许可证。
此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:
- 当前线程获取了1个可用的许可证,则会停止等待,继续执行。
- 当前线程被中断,则会抛出InterruptedException异常,并停止等待,继续执行。
看看java底层代码:
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
等资源充足,使用cas操作成功后,才停止阻塞,表示当前线程获得某种资源。
③,semaphore.release();// 跑完了,释放跑道
当前线程释放1个可用的许可证。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
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;
}
}
三,CyclicBarrier
3.1,实战
项目需求:
3个人跑步,最后一个人跑完,计算总用时。
代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest {
public static void main(String[] args) {
List<Integer> times = new ArrayList<>();
CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
@Override
public void run() {
Integer count = 0;
for(Integer time:times){
count += time;
}
System.out.println("一共用时:" + count);
}
});
Thread runner1 = new MyCyclicBarrierthread("豪子", barrier, times);
Thread runner2 = new MyCyclicBarrierthread("李子", barrier, times);
Thread runner3 = new MyCyclicBarrierthread("刚子", barrier, times);
System.out.println("开始比赛。。。");
runner1.start();
runner2.start();
runner3.start();
}
}
class MyCyclicBarrierthread extends Thread{
private String runner;
private CyclicBarrier cyclicBarrier;
private List<Integer> times;
public MyCyclicBarrierthread(String runner, CyclicBarrier cyclicBarrier, List<Integer> times){
this.runner = runner;
this.cyclicBarrier = cyclicBarrier;
this.times = times;
}
@Override
public void run() {
long s1 = System.currentTimeMillis();
System.out.println(runner + "begin run");
try {
Thread.sleep(new Random().nextInt(4)*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(runner + "end run");
long s2 = System.currentTimeMillis();
times.add((int) (s2 - s1));
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
运行结果:
开始比赛。。。
豪子begin run
刚子begin run
李子begin run
豪子end run
刚子end run
李子end run
一共用时:9003
可以看到,在主线程内并没有使用类似countdownlatch的结束,等所有线程结束。而是使用循环屏障到达一定条件后自动触发。这点和countdownlatch有点类似。
3.2,原理
在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用CyclicBarrier的await方法时,将剩余拦截的线程数减1,然后判断剩余拦截数是否为0,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁,接着先从await方法返回,再从CyclicBarrier的await方法中返回。
①,先看看构造器:
/** The lock for guarding barrier entry */
private final ReentrantLock lock = new ReentrantLock();
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
可以看到,类实例变量lock。
②,cyclicBarrier.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)
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();
}
}
每当一个线程调用CyclicBarrier的await方法时,将剩余拦截的线程数减1,然后判断剩余拦截数是否为0,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁,接着先从await方法返回,再从CyclicBarrier的await方法中返回。
CyclicBarrier主要用于一组线程之间的相互等待,而CountDownLatch一般用于一组线程等待另一组些线程。实际上可以通过CountDownLatch的countDown()和await()来实现CyclicBarrier的功能。即 CountDownLatch中的countDown()+await() = CyclicBarrier中的await()。注意:在一个线程中先调用countDown(),然后调用await()。

本文深入解析了CountDownLatch、Semaphore及CyclicBarrier三种并发控制工具的使用场景、实战案例及其内部工作原理,帮助读者掌握线程间同步的具体实现。
1625

被折叠的 条评论
为什么被折叠?



