一、CountDownLatch
CountDownLatch能够让一个线程在等待其他线程全部完成各自任务后再执行。而CountDownLatch是通过计数器来实现的,计数器的初始值即为任务的总数。
举个例子,如,同学聚会结束回家,每个人都要回各自的家,此时计数器的初始值为参加聚会的总人数,而每个人都是一个线程,每个同学到家后,都需要调用countDown方法,对计数器减一,表示完成回家的任务,当所有同学都到家后,主线程才可以执行通知班长全部到家的任务。再比如,所编写的应用程序,希望等待启动框架的线程启动完毕后再执行。
1.1 构造方法
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
CountDownLatch的构造方法需要一个count参数,代表初始任务的数量,往后每当调用一次(CountDownLatch.countDown()方法,count都减一,当count减到0的时候,调用CountDownLatch.await()方法的线程就可以执行其任务。
除此之外,CountDownLatch还有以下几个方法:
1.await(long timeout,TimeUnit unit)
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
此方法在await()的基础上添加了时间限制,如果调用await的线程在到达该时间后,count仍然没有0,其会继续执行,不再等到count到0。
2. public long getCount()
public long getCount() {
return sync.getCount();
}
即获取该CountDownLatch当前的count值
二、Demo应用
package com.wml.test1.countdownlatch;
import java.util.concurrent.CountDownLatch;
/**
* @author Wang
* @date 2020/1/2317:27
*/
public class CountDownLatchTest {
//1.
private static CountDownLatch finishEat=new CountDownLatch(1);
//2.
private static CountDownLatch friends=new CountDownLatch(10);
public static void main(String[] args) throws InterruptedException {
//3.
for (int i = 0; i <10; i++) {
new Thread(()->{
try {
//4.
finishEat.await();
System.out.println(Thread.currentThread().getName()+"正在回家");
//5.
friends.countDown();
System.out.println(Thread.currentThread().getName()+"到家啦");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"同学"+(i+1)).start();
}
System.out.println("聚会结束,同学们准备回家");
//6.
finishEat.countDown();
//7.
friends.await();
//8.
System.out.println("同学全部到家");
}
}
说明:
1.代码1创建一个结束聚餐的CountDownLatch,finishEat。初始值为1,代表正在聚餐,当主线程调用countDown()时(代码6),count值从1变为0,代表结束聚餐。这时,其他调用finishEat.await();
的线程(各个同学回家的线程)由阻塞状态变为执行。
2.代码2创建一个同学回家的CountDownLatch,friends。初始值为10,代表参加聚会的10个同学,即10个同学需要执行回家任务。回家的任务需要在代码4finishEat.await();
后执行。
3.代码3创建10个回家的线程,每个线程中都执行一次friends.countDown();
,让friends的count减一
4.代码7friends.await();
主线程阻塞等待friends的count变为0时开始执行下面的代码。
结果:
聚会结束,同学们准备回家
同学1正在回家
同学1到家啦
同学2正在回家
同学2到家啦
同学4正在回家
同学4到家啦
同学3正在回家
同学3到家啦
同学5正在回家
同学5到家啦
同学6正在回家
同学6到家啦
同学全部到家
三、CyclicBarrier
Cyclic(循环的)Barrier(屏障),该工具做的事情是,当一个线程到达一个屏障(同步点/临界点)时会被阻塞,等待到最后一个线程到达该点后,被拦截阻塞的线程才可以继续执行。
比如:同学聚餐,不会是到一个就吃一个,而是到的人先等待,直到所有人都达到饭桌后,才开始吃饭。这里餐桌就类似barrier,每个同学都是一个线程,每个人到饭桌后都被阻塞,直到最后一个同学到达。
而因为CyclicBarrier是可循环的,当一组线程到达后,其仍然有效,可以继续下一组循环。
3.1构造方法
1.默认构造方法
public CyclicBarrier(int parties) {
this(parties, null);
}
传入的参数表示需要拦截的线程总数,每当一个线程调用CyclicBarrier.await()
方法后,会通知CyclicBarrier该线程已到达屏障。
与CountDownLatch一样,CyclicBarrier也为await提供了超时时间设置。
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
线程阻塞直到到达超时时间。
2.高级构造方法
public CyclicBarrier(int parties, Runnable barrierAction)
该方法使得线程到达障碍后,优先执行barrierAction的操作。
3.2 CyclicBarrier的方法
3.2.1 await
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
该方法等待所有的线程都到达指定的临界点。
但如果当前线程不是最后到达的线程,则出于线程调度目的将其禁用,并使其处于休眠状态,直到发生以下情况之一:
- 最后一个线程到达
- 其他线程中断当前线程
- 其他线程中断等待线程之一
- 在屏障等待时其他线程超时
- 其他线程在此屏障上调用reset方法
在await
方法中调用的dowait
方法
以下是dowait
源码:
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();
//代码1
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//代码2
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();
}
}
// 代码3
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();
}
}
说明:
代码1:如果当前线程被中断,会调用breakBarrier
方法并抛出InterruptedException
异常。breakBarrier
方法如下:
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
1.breakBarrier
首先将该代的broken
设置为true
,代表其被打破。
2.generation
是CyclicBarrier
的一个静态内部类Generation
的对象,该对象代表屏障的当前代,可以实现循环等待。broken为true,则该循环结束。
3.同时将count
重置为parties
;count
为计数器,parties
是CyclicBarrier
的构造参数,代表拦截的线程数
4.调用trip
的signalAll()
方法,将所有阻塞的线程唤醒trip
是成员变量Condition
的对象,可见是使用Condition实现阻塞队列的。
代码2:
计数器count
减一并赋值给int变量index
,如果此时index值为0,则判断当前barrierCommand
是否为空,(barrierCommand
也是CyclicBarrier
的成员变量,为换代前优先执行的任务,其在CyclicBarrier
构造方法中传入,如下:
public CyclicBarrier(int parties, Runnable barrierAction) {
..........
...........
this.barrierCommand = barrierAction;
}
),如果不为空,则执行该操作的run()
方法。
最后置ranAction
为true
,个人理解代表继续执行,并执行nextGeneration()
进入下一代(下一个循环)。
nextGeneration()代码如下:
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
在nextGeneration()
中唤醒所有阻塞进程,重置计数器,并重新new了一个Generation。
而在finally中,如果barrierCommand执行出错,或其他原因,会执行breakBa
在这里插入代码片rrier()
方法。
代码3:
无限循环直到发生tripped,断开,中断或超时。timed
代表是否开启超时时间,nanos
为设定的超时时间
3.2.2 getNumberWaiting
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
}
}
返回当前有多少个线程阻塞等待在屏障上
3.3 isBroken
boolean isBroken()
返回查询阻塞等待的线程是否被中断打破
四、CyclicBarrier 例子
package com.wml.test1.cyclicbarrier;
import java.util.concurrent.*;
/**
* @author Wang
* @date 2020/1/2317:44
*/
public class CyclicBarrierTest {
//代码1
private static CyclicBarrier barrier=new CyclicBarrier(6,()->{
System.out.println("同学们都到齐了,咱们开饭");
});
public static void main(String[] args) {
ExecutorService pool = new ThreadPoolExecutor(6,100,0L,TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
for (int i = 1; i <= 6; i++) {
pool.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + "到了");
//代码2
barrier.await();
System.out.println(Thread.currentThread().getName() + "开吃");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
}
}
}
说明:
该例子为同学聚餐,每到一个同学便阻塞自己,当所有同学都到齐后,输出“开饭”,然后唤醒所有同学,执行“吃饭”
代码1:构造一个CyclicBarrier 屏障,拦截线程数为6,在进入下一代前执行System.out.println("同学们都到齐了,咱们开饭");
这一barrierCommand
,即当前代结束(所有线程都到达屏障时或其他原因)后优先执行该操作。
代码2:
每个线程到达屏障后执行await
方法,直到所有线程都到达,await中会执行唤醒所有阻塞线程的方法,最后才会执行后面的代码。
结果:
pool-1-thread-1到了
pool-1-thread-2到了
pool-1-thread-3到了
pool-1-thread-4到了
pool-1-thread-5到了
pool-1-thread-6到了
同学们都到齐了,咱们开饭
pool-1-thread-6开吃
pool-1-thread-2开吃
pool-1-thread-3开吃
pool-1-thread-5开吃
pool-1-thread-4开吃
pool-1-thread-1开吃
五、CountDownLatch和CyclicBarrier的异同
1.CountDownLatch技术器只能使用一次,CyclicBarrier的可以循环使用。
2.CountDownLatch是一个线程等待所有线程完成任务后才会执行自己的任务,是阻塞工作线程。CyclicBarrier是所有线程到达某个屏障(临界点)后互相等待,直到所有线程都到达后再共同进行下面的任务。
3.CountDownLatch调用countDown方法可以继续执行后面的,只是调用await方法的线程会阻塞等待直到所有线程都调用countDown;CyclicBarrier是所有的线程调用await进行自阻塞,等到所有线程都调用一次await后一起继续执行后面操作。
4.CyclicBarrier可以在所有线程都到达屏障后执行barrierAction操作,完成复杂的逻辑场景。
六、Semaphore
Semaphore即信号量,是用来控制同时访问特定资源的线程数量,或同时执行某个指定操作的数量。比如可以用来实现数据库的连接池等。
如数据库的连接数为20个,这时只能有20个线程同时获取数据量连接,再多的线程只能阻塞等待,当一个线程归还连接后,阻塞的线程才能继续获取。
Semaphore的构造方法:
public Semaphore(int permits)
传入一个整型permits,代表可用的许可证数量。操作时必须先获取许可证,才能继续操作,当操作完成后需释放许可证,其余没有获得许可证的便阻塞等待,直到有线程释放了许可证。
线程使用Semaphore的acquire()方法获取许可证,使用release()方法释放许可证,使用tryAcquire()方法尝试获取许可证.
其他方法:
intavailablePermits() //返回此信号量中当前可用的许可证数。
intgetQueueLength() //返回正在等待获取许可证的线程数。
booleanhasQueuedThreads()//是否有线程正在等待获取许可证。
void reducePermits( int reduction) //减少 reduction 个许可证
Collection getQueuedThreads() //返回所有等待获取许可证的线程集合
semaphore示例:
package com.wml.test1.semphore;
import java.sql.Connection;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.*;
/**
* @author Wang
* @date 2020/1/2416:31
*/
public class SemphoreTest {
private final static int POOL_SIZE=10;
private final Semaphore connections;
//连接池
private static LinkedList<Integer>pool;
public SemphoreTest() {
pool=new LinkedList<>();
//初始化连接池
for (int i = 0; i < POOL_SIZE; i++) {
pool.addLast(i);
}
this.connections = new Semaphore(POOL_SIZE);
}
//释放连接
public void release(Integer conn){
if(conn!=null) {
System.out.println("当前有"+connections.getQueueLength()+"个线程等待数据库连接!!"
+"可用连接数:"+connections.availablePermits());
synchronized (pool) {
pool.addLast(conn);
}
connections.release();
}
}
//获取连接
public Integer acquire() throws InterruptedException {
connections.acquire();
Integer conn;
synchronized (pool){
conn=pool.removeFirst();
}
return conn;
}
public static void main(String[] args) {
SemphoreTest dbPool=new SemphoreTest();
ExecutorService pool = new ThreadPoolExecutor(50,100,0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
//50个线程获取连接
for (int i = 0; i < 50; i++) {
pool.execute(()->{
try {
Integer conn=dbPool.acquire();
System.out.println(Thread.currentThread().getName()
+"--------获取数据库连接");
Thread.sleep(1000);
System.out.println("归还连接");
dbPool.release(conn);
} catch (InterruptedException e) {
}
});
}
}
}
来源:CSDN
作者:一颗小陨石
链接:https://blog.csdn.net/weixin_43696529/article/details/104078687