线程池的使用

作者:Java马剑威
链接:https://www.zhihu.com/question/299968861/answer/2650651340
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

一. 前言

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

二. 线程池简介

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

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实现的。这样一来,不仅这个需求解决了,而且代码的逼格还有点高哦。

四. 小结

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

  • 线程池适合处理耗时的任务,可以充分使用目前服务器的硬件资源,加快处理的速度;
  • 使用线程池时,不要死板,要结合自己的实际业务需求;
  • 先写出基本的代码,然后再进行测试,一定要测试,至于说线程池的核心参数该如何设置,还是得根据你们实际的请求来决定。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值