一、为什么需要线程池
在介绍之前我们先说一下为什么要有线程池,在没有线程池之前,我们通常要通过new Thread()手动创建线程池并发布任务,但这种方式存在弊端,所以我们需要线程池来实现线程复用的作用,我们需要提前创建一批线程放入池中,任务到达时刻直接用空闲的线程结束后,线程不销毁,等待下一个任务。
二、线程池的七个核心参数
线程池的行为由以下七个参数共同决定:
1.corePoolsize(核心线程数):线程池中始终存活的线程数量,即使空闲也不会被销毁
2.maximumPoolSIze (最大线程数):线程池允许创建的最大线程数量,包含核心线程和非核心线程。
3.keepAliveTime(非核心线程线程存活时间):非核心线程空闲超过此时间后,会被销毁回收。
4.TimeUnit(时间单位):空闲时间的单位,秒、毫秒等。
5.BlockingQueue(阻塞工作队列):当核心线程都在忙时,新任务会放入此队列中排队等待。
6.ThreadFactory(线程工厂):用于创建线程,可以自定义线程名、优先级等。
7.RejectedExecutionHandler(拒绝策略):当线程池和队列都满了,无法处理新任务时,采取的处理策略。
四个拒绝策略:
AbortPolicy:直接抛出 RejectedExecutionException 异常(默认)
DiscardPolicy:直接丢弃任务,不抛异常
DiscardOldestPolicy:丢弃队列队头最老任务,尝试执行当前新任务
CallerRunsPolicy:由提交任务的调用者线程执行该任务
三、线程池的执行流程
调用execute( )提交线程任务,线程池按如下逻辑处理任务:
1.判断当前运行的工作线程数是否小于核心线程数,如果未超出,直接创建核心线程进行执行任务。
2.若核心线程数已满,则线程池会将任务放入工作队列,若工作队列未满,则在工作队列中排队等待,直到有核心线程空闲,再从队列中取出线程任务。
3.若任务队列已满,判断当前工作线程数是否小于最大线程数,若未超出则创建非核心线程去执行任务。
4.如果已经达到最大线程数,没有多余线程、队列也存不下任务,触发拒绝策略处理新提交的任务。
四、Executors创建的三种常见线程池
1.newFixedThreadPool ( )固定数量的线程池
核心线程数 = 最大线程数,没有非核心线程;使用无界阻塞队列LinkedBlockingQueue。
意味着它的线程数量固定,如果任务队列足够长,就不会创建非核心线程。所有线程都是核心线程,空闲不会回收;任务过多全部堆积在队列,任务量过大可能耗尽内存。适合任务量比较稳定的场景。
2.newCachedThreadPool( )动态数量的线程池
核心线程数为 0,最大线程数为 Integer.MAX_VALUE;所有的线程都是非核心线程,非核心线程数会随着任务的增多而增多,随任务的减少而减少。空闲存活时间默认 60 秒,非核心线程空闲超过 60 秒没有新任务就会被销毁,用于节省资源。使用 SynchronousQueue 队列,队列不存储任务,任务到来直接新建线程。适合大量短时间执行的任务。
3.newSingleThreadExecutor( )单线程的线程池
该线程池只有一个核心线程,最大线程数也是 1;多余的任务会在无界工作队列中排队等待。所有任务串行执行,保证任务按提交顺序执行;线程异常终止会自动重建一个新线程继续工作。适合需要顺序执行任务的场景。
五、线程池的关闭
shutdown( ):平缓关闭,不再接收新任务,已经提交的任务继续执行完毕。
shutdownNow( ):强制关闭,尝试中断正在执行的任务,返回队列中未执行的任务列表。