1.为什么使用线程池?
- 减少新建和销毁线程的时间
- 提高响应速度
- 统一管理线程
线程池是用于管理线程的池子,用于对池子中的线程进行统一的管理。
在实际开发中,我们会有很多的业务需要处理,而每个业务都需要有线程去处理。如果当我们每要去处理一个业务时,就要调配一个线程去处理。在不使用线程池的情况下,我们每次处理都要新创建一个线程去处理,处理完之后就要销毁该线程,因为其他业务用不到该线程,而新建和销毁一个线程是很耗时间的。
而如果在线程池的情况下,我们将所有需要用到的线程存入这个线程池当中,当我们需要使用时就从线程池拿出一个去处理业务。而取出这个线程所耗费的时间比起新建和销毁的时间是少很多的。即一个线程处理完一个业务之后可以接着去处理其他业务,而不会被销毁。也即线程复用。而为了避免该线程接连处理业务时会影响到,因此我们需要在处理完业务之后,将线程的状态恢复到初始化的状态。
因此使用线程池可以减少新建和销毁线程的时间、提高响应速度、统一管理线程。
2.线程池的执行原理
- 提交任务后,会先去检查线程池中的核心线程的数量
- 如果核心线程的数量小于coreSize的话,那么就会为这个任务创建一个新的线程放入线程池当中。
- 如果核心线程的数量等于coreSize的话,就会将其放入阻塞队列中,阻塞队列会分为两种,一种是无界队列LinkedBlockingQueue一种是有界队列ArrayBlockingQueue
- 如果是无界队列LinkedBlockingQueue的话,LinkedBlockingQueue底层是一个链表的结构,理论上只要不超出磁盘空间就可以无限长,所以会直接放入LinkedBlockingQueue阻塞队列中,等待其他线程执行完,再去获取
- 如果是有界队列ArrayBlockingQueue的话,ArrayBlockingQueue底层是一个数组的结构,会先去判断ArrayBlockingQueue是否还有空间,如果有空间则直接存入,如果没有空间,则会进行下一步的判断
- 如果核心线程的数量等于coreSize的,但没有超出maximumPoolSize的大小的话,会创建新的线程来处理该任务,如果持续有新的任务到达,就会持续创建新线程直到线程数达到maximumPoolSize,这时不会接着创建。而是会直接采用拒绝策略进行处理,默认的拒绝策略是抛出一个RejectedExecutionException异常。
3.线程池的参数有哪些?
- corePoolSize:核心线程的线程大小
- maximumPoolSize:最大的线程池大小
- blockingQueue:阻塞队列
- keepAliveTime:非核心线程完成业务逻辑之后还能接着存活的时间,时间一到就会被gc回收
- TimeUnit:阻塞的时间单位
- ThreadFactory:创建线程的工厂
- RejectedExecusionHandler:拒绝策略的设置
4.线程池的大小怎么设置?
- 如果线程池太小,可能会导致业务逻辑处理不过来,会使任务堆积太多导致OOM。
- 如果线程池太大,可能会导致各个线程抢夺资源,导致CPU上下文切换过于频繁而使性能下降。 因此线程池的大小如何设置很关键。 对于任务主要可以分为两类,一类是使用CPU资源的CPU密集型任务,另一类是需要进行IO操作的IO密集型任务。
- 对于CPU密集型任务:我们可以将线程池的大小设置为CPU核数+1,即N+1,多出的一个可以避免线程因为io操作、等待锁等线程阻塞,而使得CPU资源闲置。
- 对于io密集型任务:可以设置为2n
5.线程池的类型有哪些?分别的适用场景如何?
常见的线程池类型有FixedThreadPool、SingleThreadPool、CacheThreadPool、Scheduled ThreadPool。这些都是ExecutorService的实例。
-
FixedThreadPool: 固定线程数的线程池。任何时间点,最多只有nThreads个线程处于活动状态执行任务。使用无界队列 LinkedBlockingQueue (队列容量为Integer.MAX VALUE),运行中的线程池不会拒绝任务,即不会调用RejectedExecutionHandler.rejectedExecution()方法。 maxThreadPoolSize 是无效参数,故将它的值设置为与 coreThreadPoolSize 一致。 keepAliveTime 也是无效参数,设置为0L,因为此线程池里所有线程都是核心线程,核心线程不会被回收 (除非设置了executor.allowCoreThreadTimeOut(true)) 适用场景:适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务需要注意的是,FixedThreadPool 不会拒绝任务,在任务比较多的时候会导致 OOM。
-
SingleThreadPool
只有一个线程的线程池。 使用无界队列 LinkedBlockingQueue。线程池只有一个运行的线程,新来的任务放入工作队列,线程处理完任务就循环从队列里获取任务执行。保证顺序的执行各个任务适用场景:适用于串行执行任务的场景,一个任务一个任务地执行。在任务比较多的时候也是会导致 OOM。 3. CacheThreadPool
根据需要创建新线程的线程池。 如果主线程提交任务的速度高于线程处理任务的速度时,CachedThreadPool 会不断创建新的线程。极端情况下,这样会导致耗尽cpu 和内存资源。 更用友有e作线样合里小池工作队列,当线程池有空闲线程时 SynchronousQueue.offer(Runnabletask) 提交的任务会被空闲线程处理,否则会创建新的线程处理任务 适用场景:用于并发执行大量短期的小任务CachedThreadPoo1 允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量线程,从而导致 OOM。
- ScheduledThreadPoolExecutor
在给定的延迟后运行任务,或者定期执行任务。在实际项目中基本不会被用到,因为有其他方案选择比如 quartz 。使用的任务队列DelayQueue封装了一个PriorityQueue 会对PriorityQueue队列中的任务进行排序,时间早的任务先被执行(即 ScheduledFutureTask 的 time 变量小的先执行),如果time相同则先提交的任务会被先执行( ScheduledFutureTask squenceNumber的变量小的先执行)
执行周期任务步骤:
Ⅰ、线程从 DelayQueue 中获取已到期的ScheduledFutureTask(DelayQueue.take()) 。到期任务是指 ScheduledFutureTask 的 time大于等于当前系统的时间
Ⅱ、执行这个 ScheduledFutureTask
Ⅲ、修改 ScheduledFutureTask 的 time 变量为下次将要被执行的时间
Ⅳ、把这个修改 time 之后的ScheduledFutureTask 放回DelayQueue 中( DelayQueue .add() )。 适用场景:周期性执行任务的场景,需要限制线程数量的场景。
小白学习JUC,将学习的过程中做的笔记与大家分享,希望得到大家的指导!!有不对不合理的地方请多多指教!!谢谢大家!!