定义理解
线程池其实是一种池化的技术实现,池化技术的核心思想就是实现资源的复用,避免资源的重复创建和销毁带来的性能开销。线程池可以管理一堆线程,让线程执行完任务之后不进行销毁,而是继续去处理其它线程已经提交的任务。
使用线程池的好处
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
构造参数
- corePoolSize:线程池中用来工作的核心线程数量。
- maximumPoolSize:最大线程数,线程池允许创建的最大线程数。当线程池中的线程数达到corePoolSize后,如果任务队列已满,且需要继续处理新任务,线程池会创建新线程(但总数不超过maximumPoolSize)来处理这些任务。(相当于排队区满了后,后来的线程可以直接插队)如果任务数超过了maximumPoolSize,且任务队列已满,则线程池会根据拒绝策略来处理这些无法执行的任务。
- keepAliveTime:超出 corePoolSize 后创建的线程存活时间或者是所有线程最大存活时间,取决于配置。
- unit:keepAliveTime 的时间单位。
- workQueue:任务队列,是一个阻塞队列,当线程数达到核心线程数后,会将任务存储在阻塞队列中。常见实现:BlockingQueue接口的实现类,如ArrayBlockingQueue、LinkedBlockingQueue等。
- threadFactory :线程池内部创建线程所用的工厂。
- handler:拒绝策略;当队列已满并且线程数量达到最大线程数量时,会调用该方法处理任务。
如何设置参数
1. 确定核心线程数(corePoolSize)
- CPU密集型任务:对于CPU密集型任务,通常将核心线程数设置为CPU核心数的1到2倍之间。这可以确保充分利用CPU资源,同时避免过多的上下文切换。
- IO密集型任务:对于IO密集型任务,由于线程在等待IO操作时不会占用CPU,因此可以设置更多的核心线程数。一般来说,可以将核心线程数设置为CPU核心数的2倍以上,以便在等待IO时能够处理更多的任务。
- 混合型任务:如果应用程序同时包含CPU密集型和IO密集型任务,则需要根据具体情况来平衡核心线程数的设置。
2. 确定最大线程数(maximumPoolSize)
- 资源受限的环境:在资源受限的环境中(如嵌入式系统或云服务器),需要限制最大线程数以防止过多线程占用资源。
- 高并发系统:对于需要处理大量并发请求的系统,可以适当增加最大线程数以提高系统的并发处理能力。但是,最大线程数的设置应该基于系统的负载能力和资源状况进行综合考虑。
3. 设置线程空闲时间(keepAliveTime)
- CPU密集型应用:对于CPU密集型应用,通常可以将线程空闲时间设置为较短的值,因为CPU资源非常宝贵,不希望有过多的空闲线程占用资源。在某些情况下,甚至可以将其设置为0,表示不保留非核心线程。
- IO密集型应用:对于IO密集型应用,由于线程在等待IO操作时不会占用CPU资源,因此可以将线程空闲时间设置为较长的值(如1分钟以上),以避免线程频繁启动和销毁造成的性能开销。
4. 选择任务队列(workQueue)
- 有界队列:使用有界队列可以限制任务在队列中的等待时间,避免因为任务过多而导致内存溢出。但是,如果队列长度设置过小,可能会导致任务被拒绝。(一般选择有界队列)
- 无界队列:使用无界队列可以尽可能地缓存所有任务,但是需要注意内存消耗问题。如果使用了无界队列,线程池的最大线程数参数可能会变得无效,因为线程池不会尝试创建新线程来处理队列中的任务。
5. 配置线程工厂(threadFactory)
线程工厂用于创建新线程。通过自定义线程工厂,可以设定线程的优先级、守护线程状态等属性,也可以为线程设置有意义的名字,便于在JVM中进行问题诊断。
6. 配置拒绝策略(handler)
当线程池无法处理新任务时(即线程数已达到maximumPoolSize,且任务队列已满),需要配置拒绝策略来处理这些无法执行的任务。常见的拒绝策略包括直接抛出异常、用调用者所在的线程来执行任务、忽略新任务以及抛弃队列中最老的任务等。也可以根据需要自定义拒绝策略。