线程池,顾名思义就是装线程的池子。其用途是为了帮咱们重复管理线程,避免建立大量的线程增长开销,提升响应速度。 java
做为一个严谨的攻城狮,不会但愿别人看到咱们的代码就开始吐槽,new Thread().start()会让代码看起来混乱臃肿,而且很差管理和维护,那么咱们就须要用到了线程池。编程
在编程中常常会使用线程来异步处理任务,可是每一个线程的建立和销毁都须要必定的开销。若是每次执行一个任务都须要开一个新线程去执行,则这些线程的建立和销毁将消耗大量的资源;而且线程都是“各自为政”的,很难对其进行控制,更况且有一堆的线程在执行。线程池为咱们作的,就是线程建立以后为咱们保留,当咱们须要的时候直接拿来用,省去了重复建立销毁的过程。异步
//五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) //六个参数的构造函数-1 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) //六个参数的构造函数-2 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) //七个参数的构造函数 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 复制代码
虽然参数多,只是看着吓人,其实很好理解,下面会一一解答。函数
咱们拿最多参数的来讲:spa
核心线程:在建立完线程池以后,核心线程先不建立,在接到任务以后建立核心线程。而且会一直存在于线程池中(即便这个线程啥都不干),有任务要执行时,若是核心线程没有被占用,会优先用核心线程执行任务。数量通常状况下设置为CPU核数的二倍便可。线程
线程总数=核心线程数+非核心线程数3d
非核心线程:简单理解,即核心线程都被占用,但还有任务要作,就建立非核心线程code
这个参数能够理解为,任务少,但池中线程多,非核心线程不能白养着,超过这个时间不工做的就会被干掉,可是核心线程会保留。cdn
TimeUnit是一个枚举类型,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天blog
默认状况下,任务进来以后先分配给核心线程执行,核心线程若是都被占用,并不会马上开启非核心线程执行任务,而是将任务插入任务队列等待执行,核心线程会从任务队列取任务来执行,任务队列能够设置最大值,一旦插入的任务足够多,达到最大值,才会建立非核心线程执行任务。
常见的workQueue有四种:
1.SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,若是全部线程都在工做怎么办?那就新建一个线程来处理这个任务!因此为了保证不出现<线程数达到了maximumPoolSize
而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize
通常指定成Integer.MAX_VALUE,即无限大
2.LinkedBlockingQueue:这个队列接收到任务的时候,若是当前已经建立的核心线程数小于线程池的核心线程数上限,则新建线程(核心线程)处理任务;若是当前已经建立的核心线程数等于核心线程数上限,则进入队列等待。因为这个队列没有最大值限制,即全部超过核心线程数的任务都将被添加到队列中,这也就致使了maximumPoolSize
的设定失效,由于总线程数永远不会超过corePoolSize
3.ArrayBlockingQueue:能够限定队列的长度,接收到任务的时候,若是没有达到corePoolSize
的值,则新建线程(核心线程)执行任务,若是达到了,则入队等候,若是队列已满,则新建线程(非核心线程)执行任务,又若是总线程数到了maximumPoolSize
,而且队列也满了,则发生错误,或是执行实现定义好的饱和策略
4.DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
能够用线程工厂给每一个建立出来的线程设置名字。通常状况下无须设置该参数。
这是当任务队列和线程池都满了时所采起的应对策略,默认是AbordPolicy, 表示没法处理新任务,并抛出 RejectedExecutionException 异常。此外还有3种策略,它们分别以下。
(1)CallerRunsPolicy:用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,可以减缓新任务的提交速度。
(2)DiscardPolicy:不能执行的任务,并将该任务删除。
(3)DiscardOldestPolicy:丢弃队列最近的任务,并执行当前的任务。
别晕,接下来上图,相信结合图你能大彻大悟~
说了半天原理,接下来就要用了,java为咱们提供了4种线程池FixedThreadPool
、CachedThreadPool
、SingleThreadExecutor
、ScheduledThreadPool
,几乎能够知足咱们大部分的须要了:
可重用固定线程数的线程池,超出的线程会在队列中等待,在Executors类中咱们能够找到建立方式:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
复制代码
FixedThreadPool
的corePoolSize
和maximumPoolSize
都设置为参数nThreads,也就是只有固定数量的核心线程,不存在非核心线程。keepAliveTime
为0L表示多余的线程马上终止,由于不会产生多余的线程,因此这个参数是无效的。FixedThreadPool
的任务队列采用的是LinkedBlockingQueue。
public static void main(String[] args) {
// 参数是要线程池的线程最大值
ExecutorService executorService = Executors.newFixedThreadPool(10);
}
复制代码
CachedThreadPool是一个根据须要建立线程的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
复制代码
CachedThreadPool
的corePoolSize
是0,maximumPoolSize
是Int的最大值,也就是说CachedThreadPool
没有核心线程,所有都是非核心线程,而且没有上限。keepAliveTime
是60秒,就是说空闲线程等待新任务60秒,超时则销毁。此处用到的队列是阻塞队列SynchronousQueue
,这个队列没有缓冲区,因此其中最多只能存在一个元素,有新的任务则阻塞等待。
SingleThreadExecutor
是使用单个线程工做的线程池。其建立源码以下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
复制代码
咱们能够看到总线程数和核心线程数都是1,因此就只有一个核心线程。该线程池才用链表阻塞队列LinkedBlockingQueue
,先进先出原则,因此保证了任务的按顺序逐一进行。
ScheduledThreadPool
是一个能实现定时和周期性任务的线程池,它的建立源码以下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
复制代码
这里建立了ScheduledThreadPoolExecutor
,继承自ThreadPoolExecutor
,主要用于定时延时或者按期处理任务。ScheduledThreadPoolExecutor
的构造以下:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
复制代码
能够看出corePoolSize
是传进来的固定值,maximumPoolSize
无限大,由于采用的队列DelayedWorkQueue
是无解的,因此maximumPoolSize
参数无效。该线程池执行以下:
scheduleAtFixedRate
或者
scheduleWithFixedDelay
方法时,会向
DelayedWorkQueue
添加一个实现
RunnableScheduledFuture
接口的
ScheduledFutureTask
(任务的包装类),并会检查运行的线程是否达到
corePoolSize
。若是没有则新建线程并启动
ScheduledFutureTask
,而后去执行任务。若是运行的线程达到了
corePoolSize
时,则将任务添加到
DelayedWorkQueue
中。
DelayedWorkQueue
会将任务进行排序,先要执行的任务会放在队列的前面。在跟此前介绍的线程池不一样的是,当执行完任务后,会将
ScheduledFutureTask
中的
time
变量改成下次要执行的时间并放回到
DelayedWorkQueue
中。
通常须要根据任务的类型来配置线程池大小:
若是是CPU密集型任务,就须要尽可能压榨CPU,参考值能够设为 NCPU+1
若是是IO密集型任务,参考值能够设置为2*NCPU
固然,这只是一个参考值,具体的设置还须要根据实际状况进行调整,好比能够先将线程池大小设置为参考值,再观察任务运行状况和系统负载、资源利用率来进行适当调整。
java为咱们提供的线程池就介绍到这了,墙裂建议你们仍是动手去敲一敲,毕竟实践过内心才有底。