1.倒计时协作器CountDownLatch
CountDownLatch可以用来实现一个(或者多个)线程等待其他线程完成一组特定的操作之后继续运行。
场景:cf房间中有10个玩家,9个玩家必须全部准备才能开始游戏;
代码实现:
public class CountDownLatchTest {
public static final CountDownLatch countDownLatch = new CountDownLatch(9);
public static void main(String[] args) {
new Thread(()->{
try {
countDownLatch.await();
System.out.println("游戏开始,小心爆头");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
for( int i =0;i<9;i++){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+"玩家准备完毕");
}).start();
}
}
}
CountDownLatch内部会维护一个用户表示未完成的先决操作数量的计数器。CountDownLatch.countDown()方法执行一次,计数器的数量就会减少1。CountDownLatch.await()方法在计数器不为0的时候,当前线程会被暂停,当计数器达到0时,相应实例上等待的所有线程会别唤醒。
当计数器值达到0时,计数器的值不会再发生便会。此时调用CountDownLatch.countDown()不会有异常抛出,CountDownLatch.await()的线程也不会被暂停。CountDownLatch只能使用一次。CountDownLatch.getCount()方法可以查询当前计数器的值。
2.栅栏CyclicBarrier
场景:lol游戏,在选择完英雄之后,必须等待10名玩家游戏全部加载完成之后,一起进入游戏,
CyclicBarrier的作用就是等待所有线程全部达到一个点之后再一起执行。
代码实现:
public static final CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
public static void main(String[] args) {
while (true){
for( int i =0;i<10;i++){
new Thread(()->{
try {
try {
//休眠一段时间,表示不同的加载时间
Random random = new Random();
Thread.sleep(random.nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"玩家准备完毕");
//线程暂停,等待其他玩家加载成功,再一起进入游戏
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName()+"进入游戏");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//getNumberWaiting 获取等待的数量
while (cyclicBarrier.getNumberWaiting()!=0){
//游戏没有结束耐心等待
}
System.out.println("游戏结束,等待游戏结果");
System.out.println("下一把开始,请大家准备");
}
}
调用CyclicBarrier.await()方法,当前线程被暂停,当最后一个线程调用CyclicBarrier.await()方法时,会使得使用当前实例的暂停的所有线程唤醒。与CountDownLatch的不同点是,当所有线程被唤醒之后,下一次调用await()方法又会暂停,又需要等待最后的线程都执行之后才能唤醒,是可以重复使用的。
注意:CyclicBarrier经常用来模拟高并发的测试,如果一个线程需要等待另一个线程的话,在很多场景Thread.join()即可实现效果。
CyclicBarrier方法
//获取调用await()方法线程的数量
public int getNumberWaiting();
//获取屏障的数量
public int getParties();
//等待 返回值为当前线程的索引,0表示当前线程是最后一个到达的线程
public int await();
public int await(long timeout, TimeUnit unit);
//此屏障是否处于断开状态
public boolean isBroken();
//将屏障重置为初始状态
//调用初始化之后再await()等的线程会抛异常
public void reset()
3.限流Semaphore
现在服务器有很多种限流的方式,Semaphore
是jdk提供的一种限流方式。
@RestController
public class TestController {
// 限流2 个
public static final Semaphore semaphore = new Semaphore(2);
@RequestMapping("/semaphore/test")
public String test(){
try {
semaphore.acquire();
System.out.println("1-获取资源");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
return "success";
}
@RequestMapping("/semaphore/test2")
public String test2(){
try {
semaphore.acquire();
System.out.println("2-获取资源");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
return "success";
}
@RequestMapping("/semaphore/test3")
public String test3(){
try {
boolean b = semaphore.tryAcquire(1,1000, TimeUnit.MILLISECONDS);
if(b){
System.out.println("3-获取资源");
semaphore.release();
}else{
System.out.println("3-获取资源失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return "success";
}
}
按顺序请求这三个方法输出:
1-获取资源
2-获取资源
3-获取资源失败
当前限流2个,test和test2方法同时获取资源后等待10秒释放,test3方法试着获取资源(等待1秒)。等test和test2有一个调用release释放方法之后,test3就可以成功获取资源。
注意:
- Semaphore.acquire/release方法要配对使用,使用acquire申请资源,使用release释放资源。Semaphore即使在没有申请到资源的情况下,还是可以通过release释放资源,这里就要自己通过代码进行合适的处理。和锁不同的是,锁必须获得锁才能释放锁,这里并没有这种限制。
- Semaphore.release方法尽可能的放到finally中避免资源无法归还。
- 如果Semaphore的配额为1,那么创建的实例相当与一个互斥锁,由于可以在没有申请资源的情况调用release释放资源,所以,这里允许一个线程释放另一个线程的锁。
- Semaphore在申请资源的时候不是公平的,因此,如果当前配额为0 时,所有线程在申请的时候是等待状态,一旦有线程释放资源,会有等待的线程中任意一个申请成功。