一、CountDownLatch
字面意思:倒计时锁闩
,该类可以实现一个线程在等其他多个线程执行完之后,继续执行。
入参是一个计数器的值,当一个线程执行完毕时调用countDown()
方法,计数器值会减1
,当计数器值为0
时,被await()
阻塞的线程将被唤醒。
CountDownLatch latch = new CountDownLatch(10);
大家都玩过王者荣耀的5V5
排位吧,当己方5个人准备就绪,对方5人也准备就绪时,才可以进入B/P
环节,也就是王者荣耀这个线程需要等待10位玩家的线程都准备完毕,然后才出发进入游戏的操作。
package com.duchong.concurrent;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 模拟5V5排位,10个玩家都准备就绪,才开始进入游戏
* CountDownLatch :阻塞主线程,等子线程完成
* @author DUCHONG
* @since 2020-09-03 17:43:13
*/
public class CountDownLatchDemo {
public static final int playerNum=10;
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(10);
GameThread subThread = new GameThread(latch);
try {
//模拟5V5排位
for (int i = 1; i <= playerNum; i++) {
new Thread(subThread,"player-"+i).start();
TimeUnit.SECONDS.sleep(1L);
}
//阻塞主线程
latch.await();
}
catch (InterruptedException e) {
}
System.out.println("王者荣耀:玩家全部准备就绪,开始进入游戏");
}
/**
* 游戏子线程
*/
static class GameThread implements Runnable {
private CountDownLatch latch;
public GameThread(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"---准备就绪");
}
finally {
latch.countDown();
}
}
}
}
结果
二、CyclicBarrier
字面意思: 循环屏障
,多个线程到达一个屏障点时被阻塞,直到最后一个线程到达屏障点时,屏障才会解除,所有被屏障拦截的线程继续运行。
第一个入参代表屏障接触时阻塞线程的数量。第二个入参代表屏障解除时要进行的操作
CyclicBarrier c = new CyclicBarrier(10, ()->System.out.println("屏障解除"));
await()
方法其实是调用Condition
的await()
public class CyclicBarrier {
/** The lock for guarding barrier entry */
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
private final Condition trip = lock.newCondition();
//....省略
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
//wait方法
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();
}
}
// 循环
for (;;) {
try {
if (!timed)
// 调用condition的await()方法
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();
}
}
//....省略
}
该类同样可以实现与CountDownLatch
相同的效果
package com.duchong.concurrent;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
/**
* 模拟5V5排位,10个玩家都准备就绪,才开始进入游戏
* CyclicBarrier :阻塞子线程,当等待中的子线程数到达一定数量时,跳闸。
* @author DUCHONG
* @since 2020-09-03 17:41:35
*/
public class CyclicBarrierDemo {
public static final int playerNum=10;
/**
* 屏障,初始10 当await()的线程数量达到10时,跳闸。
*/
static CyclicBarrier c = new CyclicBarrier(playerNum, ()->System.out.println("王者荣耀:玩家全部准备就绪,开始进入游戏"));
public static void main(String[] args) {
try {
//
for (int i = 1; i <= playerNum; i++) {
new Thread(new GameThread(),"player-"+i).start();
TimeUnit.SECONDS.sleep(1L);
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 游戏子线程
*/
static class GameThread implements Runnable {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"---准备就绪");
//阻塞子线程
c.await();
System.out.println(Thread.currentThread().getName()+"---进入游戏");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
结果
三、Semaphore
字面意思:信号量
,多个线程访问一个共享资源时,如果想控制对该资源访问的线程的数量,可以用这个工具类。
入参对象是一个许可的数量,如果数量大于1
,则可以作为共享锁
来使用,如果数量等于1
,则可以作为排他锁
来使用
acquire()
方法表示得到一个许可,可以对共享资源进行操作, 如果许可数量分配完了,其他线程将阻塞, 直到已得到许可的线程释放许可后,才有机会再获取许可。
release()
方法表示释放一个许可。
Semaphore semaphore=new Semaphore(5);
接着王者荣耀排位5V5
的例子来讲,当玩家进入B/P
环节,5个人,但是地图只有上中下三路,也就是说最多2个人去打野位置
package com.duchong.concurrent;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 模拟进入5V5排位游戏后的B/P环节,五个人,上中下三路,最多2个人去打野位置
* Semaphore,对资源的并发数控制
* @author DUCHONG
* @since 2020-09-03 15:46
**/
public class SemaphoreDemo {
//打野人数
public static final int wildNum=2;
//总共人数
public static final int totalNum=5;
static Semaphore semaphore=new Semaphore(wildNum);
public static void main(String[] args) {
try {
for (int i = 1; i <=totalNum; i++) {
new Thread(new GameThread(semaphore),"player-"+i).start();
TimeUnit.SECONDS.sleep(1L);
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 游戏子线程
*/
static class GameThread implements Runnable {
private Semaphore semaphore;
public GameThread(Semaphore semaphore){
this.semaphore=semaphore;
}
@Override
public void run() {
try {
//抢到打野位置,最多两个人,其他人想选打野位时阻塞,除非抢到的人选择其他路
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"---抢到打野位");
TimeUnit.SECONDS.sleep(3L);
System.out.println(Thread.currentThread().getName()+"---犹豫了一下,选择了其他路");
}
catch (Exception e){
}
finally {
//新增一个许可
semaphore.release();
}
}
}
}
结果
同一时刻只有两个player获取到打野位置(共享资源)符合预期。