从0到1构建分布式秒杀系统(1)从秒杀看ThreadPoolExecutor和CountDownLatch

CountDownLatch代码

@ApiOperation(value="秒杀一(最low实现)",nickname="科帮网")
	@PostMapping("/start")
	public Result start(long seckillId){
		int skillNum = 10;
		final CountDownLatch latch = new CountDownLatch(skillNum);//N个购买者
		seckillService.deleteSeckill(seckillId);
		final long killId =  seckillId;
		LOGGER.info("开始秒杀一(会出现超卖)");
		ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		RequestContextHolder.setRequestAttributes(sra, true);
		for(int i=0;i {
				Result result = seckillService.startSeckil(killId, userId);
				if(result!=null){
					LOGGER.info("用户:{}{}",userId,result.get("msg"));
				}else{
					LOGGER.info("用户:{}{}",userId,"哎呦喂,人也太多了,请稍后!");
				}
				latch.countDown();
			};
			executor.execute(task);
		}
		try {
			latch.await();// 等待所有人任务结束
			Long  seckillCount = seckillService.getSeckillCount(seckillId);
			LOGGER.info("一共秒杀出{}件商品",seckillCount);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return Result.ok();
	}

线程池

//创建线程池  调整队列数 拒绝服务
	private static ThreadPoolExecutor executor  = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS,
			new LinkedBlockingQueue<>(1000));

先来谈谈线程池,线程池的意义是什么?

1.减少线程的频繁创建,提高线程的利用效率

2.控制最大并发数,提高系统利用率,避免阻塞。

3.集中统一管理。

其实编码中的很多设计都来源于生活,不管是设计模式,架构模式等等。线程池的作用就是将各自为政的线程集中起立统一管理,纵观历史的发展不都是从分走向合吗,从七国争霸跨越历史到近代的军阀混战,最终都走向了统一,因为统一才会高效,才会促进社会的发展。

扯远了啊。

想要掌握一个事务必先了解它,我们先来看看ThreadPoolExecutor的类代码。

public class ThreadPoolExecutor extends AbstractExecutorService{
//第一个构造方法
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
//第二个构造方法
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
//第三个构造方法
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
//第四个也是真正的初始化构造函数
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
}

这些参数又是干嘛的呢一一来看

1.corePoolSize:核心线程数量,当线程数少于corePoolSize的时候,直接创建新的线程,尽管其他线程是空闲的。当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

2.maximunPoolSize 线程池最大线程数。如果线程数量少于线程最大数且大于核心线程数量的时候,只有当阻塞队列满了才创建新线程。当线程数量大于最大线程数且阻塞队列满了这时候就会执行一些策略来响应该线程。

3 keepAliveTime:线程没有任务执行最多保持多久时间终止。也就是当线程数量超过核心线程数量的时候,且小于最大线程数量,这一部分的线程在没有任务执行的时候是会保持直到超过keepAliveTime才会销毁

unit:keepAliveTime的时间单位。

4.workQueue  阻塞队列,存储等待执行的任务。

5.threadFactory:线程工厂,用来创建线程。

6.handler:拒绝策略。

这里我们用的是第一个构造方法

//创建线程池  调整队列数 拒绝服务
	private static ThreadPoolExecutor executor  = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS,
			new LinkedBlockingQueue<>(1000));

这里的corePoolSize是这么获取的

// 返回可用处理器的Java虚拟机的数量。
private static int corePoolSize = Runtime.getRuntime().availableProcessors();

我的笔记本运行得到的corePoolSize是8,这个应该是可以调整的。

这里的TimeUnit.SECONDS是指时间单位是秒。

看到这我们就明白了这个线程池的核心线程数是8,最大线程是9,超过101秒的空闲的线程会被销毁。执行任务的最大存储单元是1000.

LinkedBlockingQueue是啥呢

简单来说它的最大线程数是corePoolSize 也就是说当你的任务corePoolSize个线程都在执行的时候,剩余的任务只能blocking.

不会去再去创建新的线程。

ThreadPoolExecutor说完了 我们再来谈谈CountDownLatch

参考:https://www.jianshu.com/p/205a61af1205

如果还是不太了解

参考:https://blog.csdn.net/J080624/article/details/82721827

其实这里我觉的用CountDownLatch秒杀不太适合,作者可能就是想秀吧

ConutDownLatch的应用场景一般是

①某一线程在开始运行前等待n个线程执行完毕。

将 CountDownLatch 的计数器初始化为n :new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

②实现多个线程开始执行任务的最大并行性。

注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 CountDownLatch 对象,将其计数器初始化为 1 :new CountDownLatch(1),多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
 下期再见。

你可能感兴趣的:(java学习)