一、线程池的概念
首先我们来了解下线程池的相关概念,线程池是什么。池,是容器,那顾名思义线程池就是管理线程的容器。
很自然的我们会引出一个问题,就是为什么要使用线程池,而不是自己去管理多线程?在多线程应用场景中,会不断创建和销毁新的线程,而这会耗费大量的io资源,这样过度消耗系统资源则有可能会导致系统奔溃,为了避免这种情况,我们就有了线程池。
上面已经讲到了线程池是容器,是管理和调度线程的容器,其核心思想就是,允许我们去多次重复使用线程而不是去创建新的线程。线程池在系统启动时,就会创建一定量的空闲线程,程序将一个任务传递给线程池时,线程池就会启动一个线程来完成这个任务,在执行结束后,这个线程并不会被销毁,而是被线程池回收成为空闲状态等待下个任务的到来。
二、线程池的核心参数
1、corePoolSize
核心线程数,核心线程在默认情况下会一直存在即使处于空闲状态,即在没有任务的情况下线程池持有线程数。如果指定allowCoreThreadTimeOut为true,则在没有任务执行超过一定时间后,也会被销毁,所以这个参数在该情况下确切来说就成了核心线程最大数。
2、poolSize
线程池当前持有线程数,当poolSize为0时,线程池关闭,而且需要注意,线程池在任意时刻都存在 poolSize <= maximumPoolSize
3、maximumPoolSize
线程池允许的最大线程数,需要注意:
- 当 poolSize = maximumPoolSize 时,如果还有任务进入,则拒绝新增的任务,由任务拒绝策略决定
4、keepAliveTime
非核心线程存活时间,当非核心线程空闲时间超过设置的keepAliveTime时,就会被回收,当allowCoreThreadTimeOut为true时,该设置也会作用于核心线程
5、workQueue
维护等待执行的Runnable对象,当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务,常见的workQueue类型有:
- SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
- LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
- ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
- DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
6、handler
任务拒绝策略,当阻塞队列满了,且线程池中的线程数达到maximumPoolSize,如果继续提交任务,就会采取任务拒绝策略处理该任务,线程池提供了4种任务拒绝策略:
- AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,默认策略
- CallerRunsPolicy:由调用execute方法的线程执行该任务
- DiscardPolicy:丢弃任务,但是不抛出异常
- DiscardOldestPolicy:丢弃阻塞队列最前面的任务,然后重新尝试执行任务(重复此过程)
同时jdk也为我们提供了RejectedExecutionHandler接口,可以根据实际应用场景去自定义策略
三、常见的线程池
1、CachedThreadPool
可缓存线程池:线程数量无限制,如果线程池内有空闲线程就使用空闲线程执行任务,否则创建新线程
2、FixedThreadPool
定长线程池:可控制线程最大并发数,超出的线程会在队列中等待。
3、ScheduledThreadPool
定长线程池:支持定时及周期性任务执行
4、SingleThreadExecutor
单例化线程池:有且仅有一个工作线程执行任务,所有任务按照指定顺序执行,即遵循队列的入队出队规则