详解java线程池参数含义,附带简单明了的实例演示

1. 线程池参数详解

先讲讲线程池的参数含义,网上相关的说明很多,如果比较了解可以略过此处

这是ThreadPoolExecutor最全的构造器:

ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • corePoolSize: 线程池中核心线程数量(经常干活的线程的数量),后续简称为coreSize

  • workQueue当前线程数量等于coreSize的时候,新来的任务保存的地方,等着有空闲线程的时候再执行,后续简称为queue

  • maximumPoolSize当前线程数量等于coreSize并且queue也满了,这时候就得额外创建线程了,创建之后线程池中的最大线程数就是由这个值决定的,后续简称为maxSize

  • keepAliveTime空闲线程能存活的时间数值

  • unit空闲线程能存活的时间单位

  • threadFactory:创建线程的工厂,可以指定线程的名字啊等等

  • handler当前线程数等于maxSize,并且queue也满了,对于新来的任务的处理策略(是丢掉呢还是抛出异常呢)

先很形象的理解一下,真的很形象

场景:线程池就是一个包工头手下的工人,coreSize就是工人的数量,task就是搬砖,queue就是车

第二种情况:有部分工人在搬,有部分在休息,这会拖了一些砖过来,休息的人也开始搬砖了

第二种情况:每个工人都在辛苦的搬着砖,相当于当前线程数等于coreSize,但是砖还在源源不断的运过来,这时候车还有空位置呢,行吧,砖先放车上,待会空了就搬

第三种情况:每个工人都在辛苦的搬着砖,车也装满了,但是这会又来砖了,怎么办呢?包工头就想着,那要不我再临时雇两个人吧

第四种情况:每个工人都在辛苦的搬着砖,车也装满了,包工头还临时雇了两个人,这时候又拖了些砖过来,怎么办呢?包工头就想我都雇了两个临时工了,再雇人就不划算了,算了吧,这些砖让其他包工头搬吧

第五种情况:砖搬得差不多了,等了半天也没砖运过来,刚请的临时工就让他们结账走人吧,不然好亏哦,还是只留下原班人马

总结

当前线程小于coreSize时,创建新线程执行任务
当前线程等于coreSize且queue未满时,将任务放进queue,不创建新的线程
当前线程等于coreSize且queue已满时,创建新的线程来执行任务
当前线程等于maxSize且queue已满时,执行拒绝策略
当任务变少,线程开始空闲,空闲时间超过设置,则销毁多余线程(除了核心线程数量的其他线程)

2. 常见参数的计算方法

在指定参数之前先弄清楚这两个问题 
1. 任务并发有多大?
2. 每个任务执行时间?

假设一秒钟的并发量在100-200之间,每个任务执行的时间为100ms

  1. corePoolSize

    最大并发量 x 每个任务的执行时间 x 80% (保证百分之八十的任务都有现成的线程来处理)

    200 * 0.1 * 80% = 16

  2. workQueue

    每秒最大并发量 - corePoolSize x (每秒处理的任务数量)
    当任务并发量超过核心线程能处理的任务数极限了,就将多余的放入queue

    200 - 16 * (1 / 0.1)= 40

  3. maximumPoolSize

    最大并发量 x 每个任务的执行时间

    200 * 0.1 = 20

其他的参数都得根据具体的业务场景来指定了

3. 实例讲解验证

场景:在Spring aop中获取请求日志,异步保存至数据库,验证线程池参数是否满足要求

下面是核心代码:

@Slf4j
@Aspect
@Component
public class UserLogAspect {
    /**
	 * 我这里将拒绝策略指定为:new Reject() 使用备用线程继续执行任务,如果使用到了备用线程则
	 * 会打印出:【当前并发较高,超过200/s,进入备用线程保存日志数据,请优化线程池参数】
	 */
	private static ThreadPoolExecutor executor = new ThreadPoolExecutor(16,
			20,
			60L,
			TimeUnit.SECONDS,
			new LinkedBlockingQueue<>(40), Thread::new, new Reject());


	@Resource
	private LogService logService;

	@Pointcut(value = "@annotation(com.hrong.major.annotation.ClickLog)")
	public void pointcut() {

	}

	@Around("pointcut()")
	public String aroundController(ProceedingJoinPoint joinPoint) throws Throwable {
		try {
			HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
			//获取请求数据,省略相关代码
			Object response = joinPoint.proceed();
			
			Log logInfo = new Log(日志相关参数);
			executor.execute(() -> logService.save(logInfo));
			return response.toString();
		} catch (Exception e) {
			e.printStackTrace();
			log.error("出现异常:{}", e.getMessage());
			Object response = joinPoint.proceed();
			return response.toString();
		}
	}

	static class Reject implements RejectedExecutionHandler {
		private ThreadPoolExecutor rejectExecutor = new ThreadPoolExecutor(8,
				10,
				60L,
				TimeUnit.SECONDS,
				new LinkedBlockingQueue<>(20), (ThreadFactory) Thread::new);

		@Override
		public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
			log.warn("【当前并发较高,超过200/s,进入备用线程保存日志数据,请优化线程池参数】");
			rejectExecutor.execute(r);
		}
	}
}

先清空log表
在这里插入图片描述

再启动Apache ab(一个简单的压力测试工具)开始测试

ab -n 100 -c 50 "http://localhost:8081/majors/5?page=1&size=20"
100表示请求数量
50表示并发数量

1. 并发50

执行结果部分截图表示请求时间最长的是568ms
在这里插入图片描述
然后再看看控制台日志,也没有使用备用线程池的日志
在这里插入图片描述
再看看数据库,数据都成功保存了
在这里插入图片描述

2. 并发100

在这里插入图片描述
控制台
在这里插入图片描述
数据库
在这里插入图片描述

3. 并发200

在这里插入图片描述
控制台提示使用了备用线程池,提示我们该优化参数了
在这里插入图片描述
数据库,没有丢数据
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值