①. CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞
②. 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)
③. 计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t");
countDownLatch.countDown();
}, i + "").start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "\t班长关门走人,main线程是班长");
}
比如说CountDownLatch你就可以回答:前面提到了CountDownLatch也是基于AQS实现的,它的实现机制很简单,
再简单总结下:CountDownlatch基于AQS实现,会将构造CountDownLatch的入参传递至state,countDown()就是在利用CAS将state减-1,
await()实际就是让头节点一直在等待state为0时,释放所有等待的线程
CyclicBarrier原理
1、它没有像CountDownLatch和ReentrantLock使用AQS的state变量,而是使用CyclicBarrier内部维护的内部维护count变量
2、同时CyclicBarrier借助ReentrantLock加上Condition实现等待唤醒的功能
parties变量和condition队列
在构建CyclicBarrier时,传入的值是parties变量,同时也会赋值给CyclicBarrier内部维护count变量(这是可以复用的关键)
//parties表示屏障拦截的线程数量,当屏障撤销时,先执行barrierAction,然后在释放所有线程
public CyclicBarrier(int parties, Runnable barrierAction)
//barrierAction默认为null
public CyclicBarrier(int parties)
每次调用await时,会将count -1 ,操作count值是直接使用ReentrantLock来保证线程安全性
如果count不为0,则添加则condition队列中
如果count等于0时,则把节点从condition队列添加至AQS的队列中进行全部唤醒,并且将parties的值重新赋值为count的值(实现复用)
再简单总结下:CyclicBarrier则利用ReentrantLock和Condition,自身维护了count和parties变量。 每次调用await将count-1,并将线程加入到condition队列上。等到count为0时,则将condition队列的节点移交至AQS队列,并全部释放。
阻塞任务线程而非主线程
CountDownLatch和CyclicBarrier都是线程同步的工具类。可以发现这两者的等待主体是不一样的。
比如集齐7颗龙珠就能召唤神龙。
//集齐7颗龙珠就能召唤神龙
public class CyclicBarrierDemo {
public static void main(String[] args) {
// public CyclicBarrier(int parties, Runnable barrierAction) {}
CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
System.out.println("召唤龙珠");
});
for (int i = 1; i <=7; i++) {
final int temp=i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t收集到了第"+temp+"颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
共同抵达时的执行操作
屏障的抵达操作:每个线程执行时,都会碰到一个屏障,直到所有线程执行结束,然后屏障便会打开,使所有线程继续往下执行。
如果一个寝室四个人约好了去球场打球,由于四个人准备工作不同,所以约好在楼下集合,并且四个人集合好之后一起出发去球场。
private static final ThreadPoolExecutor threadPool =
new ThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
//当拦截线程数达到4时,便优先执行barrierAction,然后再执行被拦截的线程。
private static final CyclicBarrier cb = new CyclicBarrier(4, () -> System.out.println("寝室四兄弟一起出发去球场"));
private static class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + "开始从宿舍出发");
try {
cb.await();
//线程的具体业务操作
TimeUnit.SECONDS.sleep(1);
System.out.println(name + "从楼底下出发");
TimeUnit.SECONDS.sleep(1);
System.out.println(name + "到达操场");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
String[] str = {"李明", "王强", "刘凯", "赵杰"};
for (int i = 0; i < 4; i++) {
threadPool.execute(new MyThread(str[i]));
}
try {
Thread.sleep(4000);
System.out.println("四个人一起到达球场,现在开始打球");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
屏障复用
CyclicBarrier是可循环利用的屏障,顾名思义,这个名字也将这个类的特点给明确地表示出来了。可重复利用,说明该类创建的对象可以复用;CyclicBarrier是一个同步工具类,它允许一组线程互相等待,直到到达某个公共屏障点。与CountDownLatch不同的是该barrier在释放等待线程后可以重用,所以称它为循环(Cyclic)的屏障(Barrier)。
现在对CyclicBarrier进行复用…又来了一拨人,看看愿不愿意一起打:
private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
//当拦截线程数达到4时,便优先执行barrierAction,然后再执行被拦截的线程。
private static final CyclicBarrier cb = new CyclicBarrier(4, () -> System.out.println("寝室四兄弟一起出发去球场"));
private static class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + "开始从宿舍出发");
try {
cb.await();
TimeUnit.SECONDS.sleep(1);
System.out.println(name + "从楼底下出发");
TimeUnit.SECONDS.sleep(1);
System.out.println(name + "到达操场");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
String[] str = {"李明", "王强", "刘凯", "赵杰"};
for (int i = 0; i < 4; i++) {
threadPool.execute(new MyThread(str[i]));
}
try {
Thread.sleep(4000);
System.out.println("四个人一起到达球场,现在开始打球");
System.out.println();
System.out.println("现在对CyclicBarrier进行复用.....");
System.out.println("又来了一拨人,看看愿不愿意一起打:");
} catch (InterruptedException e) {
e.printStackTrace();
}
String[] str1= {"王二","洪光","雷兵","赵三"};
for (int i = 0; i < 4; i++) {
threadPool.execute(new MyThread(str1[i]));
}
try {
Thread.sleep(4000);
System.out.println("四个人一起到达球场,表示愿意一起打球,现在八个人开始打球");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Semaphore 是什么?Semaphore 通常我们叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。
使用场景:
通常用于那些资源有明确访问数量限制的场景,常用于限流 。
Semaphore常用方法说明
acquire()
获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
tryAcquire(long timeout, TimeUnit unit)
尝试在指定时间内获得令牌,返回获取令牌成功或失败,不阻塞线程。
release()
释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
hasQueuedThreads()
等待队列里是否还存在等待线程。
getQueueLength()
获取等待队列里阻塞的线程数。
drainPermits()
清空令牌把可用令牌数置为0,返回清空令牌的数量。
availablePermits()
返回可用的令牌数量。
用semaphore 实现停车场提示牌功能
每个停车场入口都有一个提示牌,上面显示着停车场的剩余车位还有多少,当剩余车位为0时,不允许车辆进入停车场,直到停车场里面有车离开停车场,这时提示牌上会显示新的剩余车位数。
业务场景 :
public class TestCar {
//停车场同时容纳的车辆10
private static Semaphore semaphore=new Semaphore(10);
public static void main(String[] args) {
//模拟100辆车进入停车场
for(int i=0;i<100;i++){
Thread thread=new Thread(new Runnable() {
public void run() {
try {
System.out.println("===="+Thread.currentThread().getName()+"来到停车场");
if(semaphore.availablePermits()==0){
System.out.println("车位不足,请耐心等待");
}
semaphore.acquire();//获取令牌尝试进入停车场
System.out.println(Thread.currentThread().getName()+"成功进入停车场");
Thread.sleep(new Random().nextInt(10000));//模拟车辆在停车场停留的时间
System.out.println(Thread.currentThread().getName()+"驶出停车场");
semaphore.release();//释放令牌,腾出停车场车位
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},i+"号车");
thread.start();
}
}
}
用semaphore 实现防止商品超卖
package com.limiting.semaphore;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 秒杀防止商品超卖现象
*/
public class SemaphoreCommodity {
//商品池
private Map<String, Semaphore> map=new ConcurrentHashMap<>();
//初始化商品池
public SemaphoreCommodity() {
//手机10部
map.put("phone",new Semaphore(10));
//电脑4台
map.put("computer",new Semaphore(4));
}
/**
*
* @param name 商品名称
* @return 购买是否成功
*/
public boolean getbuy(String name) throws Exception {
Semaphore semaphore = map.get(name);
while (true) {
int availablePermit = semaphore.availablePermits();
if (availablePermit==0) {
//商品售空
return false;
}
boolean b = semaphore.tryAcquire(1, TimeUnit.SECONDS);
if (b) {
System.out.println("抢到商品了");
///处理逻辑
return true;
}
}
}
public static void main(String[] args) throws Exception {
SemaphoreCommodity semaphoreCommodity=new SemaphoreCommodity();
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
System.out.println(semaphoreCommodity.getbuy("computer"));
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
用semaphore 实现接口限流
切面注解SemaphoreDoc
package com.limiting.semaphore;
import java.lang.annotation.*;
@Documented
@Target({ElementType.METHOD})//作用:方法
@Retention(RetentionPolicy.RUNTIME)
public @interface SemaphoreDoc {
String key(); //建议设置不然可能发生,不同方法重复限流现象
int limit() default 3;
int blockingTime() default 3;
}
切面类SemaphoreAop
@Component
@Aspect
public class SemaphoreAop {
//这里需要注意了,这个是将自己自定义注解作为切点的根据,路径一定要写正确了
@Pointcut(value = "@annotation(com.limiting.semaphore.SemaphoreDoc)")
public void semaphoreDoc() {
}
//限流池
private static Map<String, Semaphore> map=new ConcurrentHashMap<>();
@Around("semaphoreDoc()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object res = null;
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
SemaphoreDoc annotation = signature.getMethod().getAnnotation(SemaphoreDoc.class);
int blockingTime = annotation.blockingTime();
int limit = annotation.limit();
String key = annotation.key();
StringBuilder name = new StringBuilder(key+signature.getMethod().getName());//方法名
for (String parameterName : signature.getParameterNames()) {
name.append(parameterName);
}
Semaphore semaphore = map.get(name.toString());
if (semaphore == null) {
Semaphore semaphore1 = new Semaphore(limit);
map.put(name.toString(),semaphore1);
semaphore=semaphore1;
}
try {
//获取令牌
boolean b = semaphore.tryAcquire(blockingTime, TimeUnit.SECONDS);
if (b) {//如果拿到令牌了那么执行方法
try {
res = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
} else {
//在一定时间内拿不到令牌那么就访问失败
throw new Exception("访问超时,目前请求人数过多请稍后在试");
}
} finally {
//释放令牌,腾出位置
semaphore.release();
}
return res;
}
}