1. 使用线程池的好处
1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3)提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
2. 创建线程池
Executors 工具类提供了四种不同的线程池来帮助我们创建不同需求的线程池:
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行
newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
3. ThreadPoolExecutor
ThreadPoolExecutor构造函数中有以下几个参数:
corePoolSize:核心线程数,即在该线程池中运行的线程池个数。
maximumPoolSize:线程池最大线程数,即线程池最多可以创建的线程个数。
keepAliveTime:非核心线程的存活时间。
unit:keepAliveTime的单位,有7种取值。TimeUnit.DAYS、TimeUnit.HOURS、TimeUnit.MINUTES、TimeUnit.SECONDS、TimeUnit.MILLISECONDS、TimeUnit.MICROSECONDS、TimeUnit.NANOSECONDS。
workQueue:任务超过核心线程数后,被放置的阻塞队列。例如LinkedBlockingQueue。
threadFactory:线程工厂,用来创建线程。
handler:饱和策略,当阻塞队列已满,并且线程数已达到最大线程数时,会对新任务采取的策略。
4. 线程池处理流程
提交任务后,线程池先判断线程数是否达到了核心线程数(corePoolSize)。
如果未达到线程数,则创建核心线程处理任务;否则,就执行下一步;
然后线程池判断缓存队列是否满了。如果没满,则将任务添加到缓存队列中;否则,执行下一步;
然后因为缓存队列满了,线程池就判断线程数是否达到了最大线程数。如果未达到,则创建非核心线程处理任务;否则,就执行饱和策略,默认会抛出RejectedExecutionException异常
饱和策略(RejectedExecutionHandler):
当任务队列和线程池都满了时所采取的应对策略,默认是AbordPolicy,表示无法处理新任务,并抛出RejectedExecutionException异常。此外还有3种策略:
1)CallerRunsPolicy:用调用者所在的线程处理任务。此策略提供简单的反馈机制,能够减缓新任务的提交速度。
2)DiscardPolicy:不能执行任务,并将任务删除。
3)DiscardOldestPolicy:丢弃队列最近的任务,并执行当前的任务。
5. 合理配置线程池
CPU密集:指该任务要进行大量的运算操作,没有阻塞,CPU要保持全速运行;该种任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务
IO密集:指该任务会要进行大量的IO操作,即大量的阻塞,由于大部分线程都阻塞,故需要多配置线程数,2*cpu核数