多线程之线程池

1.线程池参数说明

(以JUC包提供为例):

corePoolSize(核心线程数) => 正式员工数:正常情况下我们的系统能同时工作的线程数(随时就绪)

maximumPoolSize(最大线程数) => 哪怕任务再多,也最多招多少人:极限情况,线程池最多有多少个线程

keepAliveTime(存活时间) => 空闲线程存活时间 : 非核心线程在没有任务的情况下,过多久要删除,可以释放无用的线程资源

TimeUnit (单位) => 空闲线程存活时间单位

workQueue(工作队列)=> 用于存放给线程执行的任务,存在一个队列的长度(一定要设置,会占用资源)

threadFactory(线程工厂) => 控制每个线程的生成、线程的属性(比如线程名)

handler(拒绝策略) => 拒绝策略:任务队列满的时候采取什么措施,比如抛异常、不抛异常、自定义策略

2.线程池执行机制

假设参数:核心线程2、最大线程4、等待时间20分钟、队列容量2、自定义线程工厂、默认的拒绝策略

return new ThreadPoolExecutor(2, 4, 20, TimeUnit.MINUTES, new ArrayBlockingQueue<>(2), threadFactory);

任务1来了,核心线程处于随时就位的状态,可以立刻处理任务1;

任务2来了(这里都假设任务的执行时间很长),同样可以交给核心线程处理,目前核心线程都在工作;

任务3来了,先进行队列中,等待;

任务4来了,同样进行队列中等待;

任务5来了,交给临时线程5来处理任务5(默认处理新来的任务,而不是队列里面的任务);

任务6来了,同样也可以交给临时线程6处理;

任务7来了,这时候核心线程、临时线程都在工作,队列也满了,就会执行拒绝策略;

当临时线程执行任务完毕,等待了20分钟(keepAliveTime)时间,没有新的任务执行,那么临时线程会被关闭;

重点:核心线程满了,新的任务会进队列而不是开临时线程!

3.图解线程池

3.1初始参数

初始状态:核心线程4、最大线程6、等待时间20分钟、队列容量5、自定义线程工厂、默认的拒绝策略

以公司为例子:

  • 核心线程:正式员工
  • 最大线程:总员工数(当减去正式员工,就是雇的临时员工数量)
  • 等待时间:等多长时间,雇的员工都还没项目干,就把这些临时员工开除
  • 队列容量:任务的个数
  • 自定义线程工厂:给每个员工装饰一下,相当于每个员工的号牌(给线程起名字啥的)
return new ThreadPoolExecutor(4, 6, 20, TimeUnit.MINUTES, new ArrayBlockingQueue<>(5), threadFactory);

3.2执行步骤

当任务1、任务2、任务3、任务4按时间顺序来了,都被正式员工接了;队列也还是空的;临时员工还没被雇;

当任务5来的时候,先进阻塞队列中,不是老板直接就招新员工了,正式员工干完当前的活就可以再干,好老板会排好期的!

当任务6、7、8、9相继而来,任务队列位置有空余,继续放(只要老板够压榨,队列容量使劲拉长,正式员工996+吧)

当还有新任务10来时,老板实在是忙不过来,要招实习,招的员工小E开始接任务10(默认策略,可修改)

当任务11来的时候,继续招人小F实习生来做

当又来了新的任务12,且实习生小E、小F也忙不过来了的时候,可能项目钱也不是很多,划不来,老板也不招不了更多的人了,就不做这个任务了(这就是拒绝策略)

当实习生(临时员工)的活干完了,等了一段时间(keepAliveTime)又没新任务来,就把小E和小F开了,说:”下次有任务了再招你们哈“ 🙃

其实大部分的时间应该都是正式员工在有条不紊的干着...

4.Java代码实现(spring boot)

4.1定义线程池配置类

@Configuration
public class BiThreadPoolExecutor {
    // 定义自己的线程工厂
    private final ThreadFactory threadFactory = new ThreadFactory() {
        private int count;

        @Override
        public Thread newThread(@NotNull Runnable r) {
            // 其实就是创建线程的一些额外操作
            Thread thread = new Thread(r);
            thread.setName(String.valueOf(count));
            count++;
            return thread;
        }
    };

    @Bean
    public ThreadPoolExecutor threadPoolExecutor() {
        return new ThreadPoolExecutor(4, 6, 20, TimeUnit.MINUTES, new ArrayBlockingQueue<>(5), threadFactory);
    }
}

4.2测试接口定义

package com.yupi.springbootinit.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池测试接口
 */
@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    @GetMapping("/pushTask")
    public void pushTask(){
        // 开启任务,默认的线程吃是ForkJoinPool
        CompletableFuture.runAsync(() -> {
            System.out.println("当前线程名称:" + Thread.currentThread().getName());
            try {
                Thread.sleep(600000); // 60分钟
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, threadPoolExecutor);
    }
}

4.3输出结果

调用四次pushTask接口输出:核心线程接到任务

调用第五次接口输出:这时候新的任务进入了任务队列,同样调用第6、7、8、9都是在任务队列中

当第十次调用任务的时候:最大线程数来接管新的任务了,同样11次调用也是如此

当第12次调用的时候,核心线程数、最大线程数、任务队列都满了,这时候会执行拒绝策略,默认抛异常

5.总结

具体的线程池参数一定要根据项目的业务逻辑去设定,第一次肯定是不合适的,只有通过反复的测试,才能确定好线程池参数。

  • 33
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值