ITPub博客

首页 > 数据库 > 数据库开发技术 > 前方高能!Java项目中到底该怎么使用线程池?

前方高能!Java项目中到底该怎么使用线程池?

原创 数据库开发技术 作者:Finally_m 时间:2022-08-29 08:10:29 0 删除 编辑
点击上方蓝字关注我们

一. 前言

最近有不少小伙伴私聊辉哥,问我能不能讲讲项目中怎么使用线程池?所以,今天辉哥就给大家安排一下,满足粉丝们的要求,给大家讲一下之前我在项目中使用线程池的一些经验。

二. 线程池简介

我们想要在项目中使用线程池,就必须要知道线程池是啥、咋用?所以请各位先跟着辉哥学学关于线程池的基础知识吧。

2.1 线程池

我们先来看看《阿里巴巴Java开发规范》中对于线程池的使用规范,其中有如下要求:

我们平时开发使用时,一般都是使用Java原生的线程池API,也就是ThreadPoolExecutor来创建线程。我们利用该API进行统一的线程管理,在使用时只需要调用添加任务即可。

2.2 代码演示

接下来就是一段线程池的使用代码演示,大家可以参考一下。

@Test
public void thread(){
    //池化技术 :1.复用率 2.有效控制 3.性能
    //原生线程池 7个参数
    /**
     * 使用原生线程池创建
     * 7个参数
     * 1.核心线程数
     * 2.最大线程数
     * 3.空闲时间
     * 4.空闲时间的时间单位
     * 5.阻塞队列 7种
     * 6.线程工厂
     * 7.拒绝策略 4种
     * 线程池工作原理:先核心线程-->再阻塞队列-->最大线程数-->拒绝策略*/

    ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor(4,10,
            10, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10),
            Executors.defaultThreadFactory() ,
            new ThreadPoolExecutor.AbortPolicy());
    //设置是否回收核心线程
    poolExecutor.allowCoreThreadTimeOut(true);
    //线程池的线程预热
    poolExecutor.prestartAllCoreThreads();
    //添加任务
    poolExecutor.execute(()->{
        System.err.println("遇见辉哥说:线程池在处理任务");
    });
}

2.3 线程池的工作过程

那么上述代码中的线程池,是怎么处理线程任务的呢?且来听辉哥给你唠唠,线程池接收到一个线程任务后,会经过如下步骤:

  • 如果发现线程池中有空闲线程,则直接执行该任务;

  • 如果没有空闲线程,且当前运行的线程数少于corePoolSize,则创建新的线程执行该任务;

  • 如果没有空闲线程,且当前的线程数等于corePoolSize,同时阻塞队列未满,则将任务入队列,而不添加新的线程;

  • 如果没有空闲线程,且阻塞队列已满,同时池中的线程数小于maximumPoolSize ,则创建新的线程执行任务;

  • 如果没有空闲线程,且阻塞队列已满,同时池中的线程数等于maximumPoolSize,则根据构造函数中的 handler 指定的策略来拒绝新的任务。

以上就是关于线程池的一些基本常识。

三. 项目中使用线程池

言归正传,咱们再来说一下在项目中到底该如何使用线程池。辉哥之前给某个公司做过一个项目,其中有个业务,要满足大批量Excel格式的数据上传并导入到数据库中。辉哥本以为只是几百条数据,结果在对接时才发现,对方的数据量都是上万级别的批量数据导入。

来吧,直接让你看看辉哥的实现代码。

@Override
public R uploadExcel(MultipartFile file) throws IOException {
    //1.获取上传的内容 2.解析文件 3.线程池批量新增
    //1.验证上传文件是否非空
    if(!file.isEmpty()){
        //2.获取上传内容
        InputStream is=file.getInputStream();
        
        //3.解析Excel
        EasyExcel.read(is,Department.class,new PageReadListener<Department>((list)->{
            
            //使用线程池,保证每个线程处理的数据量不超过1000条
            //每个线程处理1000条数据,然后根据数据进行分片,每次计算每个任务完成的数据量的范围
            int i=0;
            int j=list.size()%1000==0?list.size()/1000:list.size()/1000+1;
            //循环添加任务到线程池中
            for(int m=1;m<=j;m++){
                ArrayList l=new ArrayList();
                l.addAll(list.subList((m-1)*1000,m*1000));
                
                ThreadPoolUtil.getInstance().poolExecutor.execute(()->{
                    //4.调用 dao 实现批量新增
                    dao.addBatch(l);
                });
            }
        })).sheet().doRead();
    }
    
    return R.ok();
}

有没有发现,辉哥自己封装了一个线程池的工具类?对!这里就是辉哥自己封装的工具模板,具体代码如下:

//采用单例模式的IoDH封装线程池的工具类
public class ThreadPoolUtil {
    //线程池对象
    public ThreadPoolExecutor poolExecutor;
    //构造函数私有化
    private ThreadPoolUtil(){
        //完成线程池对象的实例化
        poolExecutor=new ThreadPoolExecutor(4,10,3, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(20), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
    }
    //静态内部类 完成对象的实例化 
    private static class PoolInner{
        private final static ThreadPoolUtil pool=new ThreadPoolUtil();
    }
    //获取唯一实例
    public static ThreadPoolUtil getInstance(){
        return PoolInner.pool;
    }
}

辉哥的代码中不仅使用了线程池,而且用到了设计模式,这里用的是单例设计模式,而且还是基于IoDH实现的。这样一来,不仅这个需求解决了,而且代码的逼格还有点高哦。

四. 小结

好了,辉哥想表达的就这么多,但最后还是要再给大家啰嗦几句。

  • 线程池适合处理耗时的任务,可以充分使用目前服务器的硬件资源,加快处理的速度;

  • 使用线程池时,不要死板,要结合自己的实际业务需求;

  • 先写出基本的代码,然后再进行测试,一定要测试,至于说线程池的核心参数该如何设置,还是得根据你们实际的请求来决定。

如果你还有其他的问题,可以关注本公众号并私聊辉哥,让我们在学习的路上一起成长吧。

本月福利,关注公众号回复【福利】抢书咯!

今日份干货,学会的同学回复“666”!

精选第一名,可以领取办公鼠标垫一个!

点击【阅读原文】,零基础免费学Java!

原文作者:Java架构栈;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2021-04-13

  • 博文量
    573
  • 访问量
    243927