线程池的详解

本文详细介绍了线程池的核心参数、执行流程及应用场景。包括线程池的配置原则、线程池拒绝策略、线程池的工作队列实现原理,以及如何区分CPU密集型与IO密集型任务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程池的详解

一、线程池的核心参数:

CorePoolSize 核心线程数:线程池维护的最少线程数,不会被回收,但是设置了allowCoreThreaTimeOut = true的时候也会被回收
MaxmumPoolSize 最大线程数:线程池最多维护线程的数量。当添加一个任务,但是核心线程数已经满了的时候就会创建线程,但是不会超过最大线程数。
KeepAliveTime 空闲线程存活时间:超过核心线程数的线程空闲的时间,如果超过了该时间则会被回收。当然核心线程数上面的参数如果设置了,核心线程也会被回收。
Unit 时间单位:KeepAliveTime的时间单位
workQueue 工作队列:存放待执行任务的队列:当提交的任务数超过核心线程数,提交的任务就会被放到该队列里面来,任务调度的时候再从队列中取出。它仅仅存放被execute()提交的Runnable任务。工作队列实现了BlockingQueue接口。

  1. ArrayBlockingQueue :数组型阻塞队列,初始化的时候传入大小,有界,FIFO,使用一个重入锁,非公平锁,然后入队和出队时共用一个锁,互斥。
    重入锁: 可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁比如synchronized,ReentrantLock。非公平锁: 非公平锁指的是不完全按照请求的顺序,在一定情况下,可以允许插队。互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作。FIFO:First In First Out)
  2. LinkedBlockingQueue:链表型阻塞队列:默认初始化大小Interage.MAX_VALUE,有界(但近似无界),FIFO,使用两个重入锁分别控制元素的进队和出队,用Condition进行线程间的唤醒和等待。
  3. SynchronousQueue 同步队列:容量为0,添加任务必须等待取出任务,这个队列相当于通道,不存储元素。
  4. PriorityBlockingQueue 优先阻塞队列:无界,默认采用元素自然顺序升序排列。
  5. DelayQueue 延时队列:无界,元素有过期时间,过期的元素才能被取出。

threadFactory:线程工厂 :创建线程的工厂,可以设定线程名、线程编号等。

默认线程工厂:

    /**
     * The default thread factory
     */
    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;
 
        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }
 
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

handler:拒绝策略:
当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需要实现RejectedExecutionHandler接口。
JDK默认的拒绝策略有四种:
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。(默认)
DiscardPolicy:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态。
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
CallerRunsPolicy:由调用线程处理该任务。

自定义拒绝策略:实现java.util.concurrent.RejectedExecutionHandler接口。

二、线程池的执行流程:

  1. 查看线程是否在运行:是就第2步,不是则执行拒绝策略,结束
  2. 查看是否超过了核心线程数,是就查看第3步,不是就创建线程执行任务,结束。
  3. 查看是否超过了workQueue是否满了,是就查看第4步,不是就添加到队列中等待被调用,结束
  4. 查看线程数是不是超过了最大线程数,是就执行拒绝策略,不是就创建线程并执行,结束

三、应用案例

  1. 异步发送邮件通知(ThreadPoolExecutor)
    发送一个任务,然后注入到线程池中异步发送。

  2. 心跳请求任务(ScheduledThreadPoolExecutor)
    创建一个任务,然后定时发送请求到线程池中。

  3. POJO的组装任务(ThreadFixedPoolExecutor)
    提取线程然后执行。

四、源码分析

ThreadPoolExecutor的父类是AbstractExecutorService,然后AbstractExecutorService的顶层接口是:ExecutorService
就例如发送邮件接口而言,当线程池触发了submit函数的时候,实际上会调用到父类AbstractExecutorService对象的java.util.concurrent.AbstractExecutorService#submit(java.lang.Runnable)方法,然后进入到ThreadPoolExecutor#execute部分。

五、线程池的合理配置

如果想合理的配置线程池,就需要了解我们任务的类型,是CPU密集型还是IO密集型,
1)CPU 密集型任务主要时间花费在计算上面,内存、硬盘、网络占用的时间少于cpu本身计算的时间,这时应配置尽可能小的线程,避免线程之间频繁的切换消耗资源,如配置 Ncpu+1 个线程的线程池。
例如:文件读写、DB读写、网络请求等
2)由于 IO 密集型任务线程并不是一直在执行任务,cpu的性能,消耗的时间少于请求内存、硬盘、网络的时间,这时大部分时间cpu处于空闲状态,则应配置尽可能多的线程,如 2*Ncpu。
例如:计算型代码、Bitmap转换、Json转换等

六、为什么要合理配置线程池

为了不占用主线程 -> 所以启一个新线程 -> 频繁的new线程又会带来大量的开销 -> 所以使用线程池进行复用 -> 而不合理的线程池设计又会带来线程使用低效,甚至新加入的任务只能等待 -> 优化线程池

七、如何判断自己的业务是IO密集型还是CPU密集型

可以在这个方法的开始、结束调用 TaskInfoUtils.buildCurTaskInfo();然后调用 TaskBoundJudge.isCpuTask(start,end), TaskBoundJudge.isIOTask(start,end)即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值