线程池的好处
- 降低资源消耗: 不用线程池的话,线程的创建和销毁都要消耗资源;而线程池的复用可以避免这种资源消耗。
- 提高响应速度: 不用线程池的话,当任务到达时,需要先创建一个线程才能执行;而线程池中任务则可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性: 不用线程池的话,大量线程难以管理;而线程池可以进行统一的分配,调优和监控。
用Executors创建线程池
用 Executors
创建的常见线程池类型:
newCachedThreadPool
: 线程数量可变的线程池,空闲线程空闲久了(默认一分钟)就会终止。newFixedThreadPool
: 线程数量固定的线程池,最经典的线程池,如果所有线程都不空闲,新任务会被放入任务队列,排队等有空闲线程再被执行。newSingleThreadExecutor
: 只有一个线程的线程池,任务会被保存在任务队列中,排队被执行。newScheduleThreadExecutor
: 线程数量固定的线程池,用于执行定时任务。
然而,这几种线程池有可能产生 OOM 问题。
newCachedThreadPool
的线程数最大可达 Integer.MAX_VALUE
,newFixedThreadPool
和 newSingleThreadExecutor
的任务队列长度最大可达 Integer.MAX_VALUE
。
所以一般都用 ThreadPoolExecutor
。
用ThreadPoolExecutor创建线程池
ThreadPoolExecutor
线程池的 7 个核心参数:
corePoolSize
: 核心线程数,只要线程池还在,就始终至少有这么多线程活着。maximumPoolSize
: 最大线程数,如果任务队列满了,可同时运行的线程数就会变为这个最大线程数,拉满了跑!keepAliveTime
: 非核心线程的心跳时间,如果非核心线程在这么长的时间里一直空闲,就会死亡。unit
: 心跳时间keepAliveTime
的计量单位。workQueue
: 任务队列,用来存现在没有空闲线程给他执行的新任务。
以上 5 个是创建时必须传入的,ThreadPoolExecutor
的4 个不同构造器都需要传入这 5 个参数。
下面 2 个创建时可以不传入,但是其实构造器会用默认的来创建,所以其实 7 个参数都必须有。
threadFactory
: 线程工厂,线性池中的线程是用工厂模式创建的。handler
: 饱和策略,也就是满了(所有线程都在工作,且任务队列也满了)的时候该怎么办。AbortPolicy
(默认): 抛异常,完事。DiscardPolicy
: 装没看见,直接把装不下的新任务扔了。DiscardOldestPolicy
: 新的顶走最老的,把队列里最老的任务(workQueue
的队首任务)扔了,放新的进去。CallerRunsPolicy
: 不接活,谁给我的这个任务就让谁执行,让调用execute()
方法的线程执行装不下的任务。
线程池常用的阻塞队列
LinkedBlockingQueue
: 用于FixedThreadPool
和SingleThreadExecutor
,这两种线程池总是有很多任务在等待,所以LinkedBlockingQueue
的容量为Integer.MAX_VALUE
。SynchronousQueue
: 用于CachedThreadPool
,这种线程池的线程容量为Integer.MAX_VALUE
,不会有很多任务在等待,所以SynchronousQueue
的容量有限,并且使用的是生产者-消费者的模式。如果生产者生产了产品没有消费者去消费,生产者就会进入阻塞。DelayedWorkQueue
: 用于ScheduleThreadPool
和SingleThreadScheduleExecutor
,这两种线程池用于执行定时任务,所以DelayedWorkQueue
的存储结构用的是堆,从而把任务按执行时间排序。
线程池的执行原理
- 核心线程还有没有空闲的?有就直接用,没有就下一步。
- 任务队列满了没?没满就让新任务进去等着,满了就下一步。
- 整个线程池满了没?没满就创建一个新线程,满了就按
handler
饱和策略来办。
可以发现,第二步说明线程池是很不想创建非核心线程的,就想着能只用核心线程就把所有事办了,非得等到等待的任务都装不下了,才肯创建非核心线程。