深入理解线程池

什么叫线程池

一个线程集合, 统一管理线程的数量, 线程的生命周期, 并尽可能重用池中的线程. 我们常用的数据库连接池也是相同思想的产物.

为什么使用

  1. 一个线程在创建和销毁时, 非常消耗资源. 为了尽可以减少线程创建和销毁的次数, 多次重用同一线程, 将资源消耗分摊到多个任务上.
  2. 不使用线程池管理线程, 若运行的线程数量过大, 可能会导致系统资源消耗过大, 出现异常.

如何使用

  1. 使用如下方式创建线程池:
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
//静态包装后的常用线程池方法
Executors.newSingleThreadExecutor(); 
Executors.newFixedThreadExecutor(int nthreads);
Executors.newCachedThreadExecutor(); 
  1. 静态方法含义
  • Executors.newSingleThreadExecutor(): 创建一个线程池, 但只包含一个线程, 单线程执行所有任务, 若该线程异常结束, 重启一个新线程.
  • Executors.newFixedThreadExecutor(int nthreads): 创建一个线程池, 添加一个任务创建一个线程, 直到线程数量等于最大线程数, 线程数则保持不变. 若有线程异常结束, 则重启一个新线程.
  • Executors.newCachedThreadExecutor(): 创建一个线程池, 线程数量由jvm和执行的任务量决定, 任务增加时, 自动创建, 线程空闲超时(60秒)后, 自动回收.

深入参数分析

  1. 线程池的接口/类继承结构:
    ThreadPoolExecutor -> AbstractExecutorService -> ExecutorService -> Executor
    Executors 是ThreadPoolExecutor工厂方法实现.
  2. ThreadPoolExecutor 完整签名 参数讲解:
  • corePoolSize: 线程池中所保存的线程数量(包括空闲线程)
  • maximumPoolSize: 线程池中所允许的最大线程
  • keepAliveTime: 当线程数大于核心数时, 销毁线程的超时时间
  • unit: keepAliveTime 的时间单位
  • workQueue: 保存待执行的任务的任务队列
  • threadFactory: 执行创建线程时所使用的工厂类
  • handler: 拒绝执行的任务的处理类
  1. 线程池线程创建逻辑

    1. 当运行线程数 < corePoolSize 时: 直接添加新线程执行任务
    2. 当任务队列满, 运行线程数 >= corePoolSize且 < maximumPoolSize 时: 创建新线程执行任务.
    3. 当任务队列满, 运行线程数 >= corePoolSize且 >= maximumPoolSize 时: 执行拒绝策略
  2. 线程池线程超时逻辑

    1. 当线程池中线程保持 keepAliveTime 时间后, 自动销毁, 超时时间的单位由 unit 决定.
  3. 任务队列的类型

    1. ArrayBlockQueue: 有界队列, 可以防止资源耗尽, 但是难以调优, 不推荐使用.
    2. LinkedBlockQueue: 无界队列, 任务之间单独执行, 不相互影响.
    3. SynchronousQueue: 直接提交队列, 不保存任务, 直接提交线程. 但是每次插入必须等待另一个线程移除操作, 否则一直阻塞.
  4. 拒绝任务执行策略

    1. CallerRunsPolicy: 直接调用线程运行该任务. 该策略觉得该任务还可以再抢救. 一般会使用执行该execute的线程执行任务
    2. AbortPolicy: 丢弃任务, 抛出异常
    3. DiscardPolicy: 直接丢弃任务, 但是不抛出异常
    4. DiscardOldestPolicy: 将队列首部的旧任务剔除, 执行该任务. 请小心使用该策略.
  5. 静态方法的默认实现及输出演示:

    1. Executors.newSingleThreadExecutor() : 内部调用 new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
    ExecutorService executor = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 4; i++) {
        final int index = i;
        executor.execute(() -> {
            System.out.print(Integer.toString(index) + Thread.currentThread().getId() + " ");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
    executor.shutdown();
    // 输出:  013 113 213 313
    // 说明结果是同一线程, 顺序执行的
    
    1. Executors.newFixedThreadExecutor(int nthreads) : 内部调用 new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
    ExecutorService executor = Executors.newFixedThreadPool(2);
    for (int i = 0; i < 4; i++) {
        final int index = i;
        executor.execute(() -> {
            System.out.print(Integer.toString(index) + Thread.currentThread().getId() + " ");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
    executor.shutdown();
    // 输出:  011 112 211 312
    // 说明结果是两个线程, 独立顺序执行的
    
    1. Executors.newCachedThreadExecutor() : 内部调用 new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue());
    ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 4; i++) {
            final int index = i;
            executor.execute(() -> {
                System.out.print(Integer.toString(index) + Thread.currentThread().getId() + " ");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executor.shutdown();
    // 输出:  011 112 213 314 
    // 说明结果是多个线程, 独立顺序执行的
    

如何配置线程池大小

  1. cpu密集型任务: 参考值 n(cpu数量) + 1
  2. io密集型任务 : 参考值 2 * n(cpu数量)
  3. 参数敏感时需要根据实际情况进行测试, 再具体调整参数

总结

  1. 推荐使用系统自带的 newSingleThreadExecutor(), newFixedThreadExecutor(), newCachedThreadExecutor() 三个创建线程池的静态方法创建线程, 能满足绝大部分情况.
  2. 自定义参数实现:
    1. 若对执行时间和顺序有要求, 建议使用优先级队列.
    2. 若任务较多可能超过系统限制, 建议使用有界队列.
    3. 实际情况建议多测试, 具体调整参数.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值