在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源,创建线程亦是如此,这导致在高并发中效率低下并且资源耗费严重,因此,池化资源技术应运而生,所以就有了线程池。
JDK为我们封装了一套操作多线程的框架Executors,常用的方法如下:
newSingleThreadExecutor:
创建一个单线程的线程池,这个线程池有且只有一个线程在工作
newFixedThreadPool:
创建固定大小的线程池,通过带参数创建指定线程数大小,每提交一个任务就会创建一个新的线程,直到达到指定线程数
newCachedThreadPool:
创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)的线程
newScheduledThreadPool:
创建一个无限大小的线程池,每提交一个请求创建一个线程,支持定时和周期性任务执行
其实这些方法最终都是创建了ThreadPoolExecutor 对象,只是传入的参数不同
在《阿里巴巴java开发规范》中是禁止使用Executors类创建线程池,必须通过合理设定ThreadPoolExecutor对象参数来创建线程池
看ThreadPoolExecutor源码有这几个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
核心线程数,当有任务进来的时候,如果当前线程数还未达到 corePoolSize 个数,则创建核心线程,核心线程有几个特点:
除了有核心线程外,有些策略是当核心线程完全无空闲的时候,还会创建一些临时的线程来处理任务,maximumPoolSize 就是核心线程 + 临时线程的最大上限。临时线程有一个超时机制,超过了设置的空闲时间没有事儿干,就会被销毁。
这个就是上面两个参数里所提到的超时时间,也就是线程的最大空闲时间,默认用于非核心线程,通过 allowCoreThreadTimeOut(boolean value) 方法设置后,也会用于核心线程。
这个参数是用来指定keepAliveTime参数的时间单位,秒、分、时等。
它是 BlockingQueue类,即阻塞队列。
当核心线程满了并且没有空闲的了,新来的任务就会被放到这个等待队列中。这个参数其实一定程度上决定了线程池的运行策略,为什么这么说呢,因为队列分为有界队列和无界队列。
有界队列: 队列的长度有上限,当核心线程满载的时候,新任务进来进入队列,当达到上限,如果没有核心线程去即时取走处理,这个时候,就会创建临时线程。(警惕临时线程无限增加的风险)
无界队列: 队列没有上限的,当没有核心线程空闲的时候,新来的任务可以无止境的向队列中添加,而永远也不会创建临时线程。(警惕任务队列无限堆积的风险)
它是一个接口,用于实现生成线程的方式、定义线程名格式、是否后台执行等等,可以用 Executors.defaultThreadFactory() 默认的实现即可,也可以使用自定义的线程工厂
当没有空闲的线程处理任务(即核心线程满了,并且都是在运行状态),并且等待队列已满(这里只对有界队列有效),再有新任务进来的话,就要做一些取舍了,而这个参数就是指定取舍策略的,ThreadPoolExecutor类提供了下面四种策略可以选择:
AbortPolicy:直接抛出异常,这是默认策略
DiscardPolicy:直接丢弃任务,但是不抛出异常
DiscardOldestPolicy:丢弃队列最前面的任务,然后将新来的任务加入等待队列
CallerRunsPolicy:由线程池所在的线程处理该任务,比如在 main 函数中创建线程池,如果执行此策略,将有 main 线程来执行该任务
(1)用户向线程池提交一个任务,线程池首先判断是否有空闲线程,如果有则使用空闲线程处理任务,
(2)如果没有空闲线程,则判断核心线程数corePoolSize是否满了,没满则创建新的线程处理任务
(3)如果核心线程满了,则判断队列workQueue是否满了,如果没满将任务添加到队列中等待有空闲线程来执行任务
(4)如果队列满了,则判断总容量maximumPoolSize是否满了,如果没满则创建临时线程处理任务,临时线程默认情况下,处于空闲状态时间超过keepAliveTime数则被回收
(5)如果总容量maximumPoolSize满了则执行线程拒绝策略handler
向线程池提交任务的方法有两种,分别是submit和execute
源码如下:
从源码可以看出execute方法是提交一个普通的Runnable对象,submit方法既可以提交Runnable对象也可以提交Callable对象,并且具有返回值返回一个Future对象,这个Future对象可以用来判断线程是否执行成功。所以当我们需要判断线程是否执行成功时,可以采用submit方法提交任务,再调用返回的Future对象的get()方法我们最终需要的返回值;如果不关心任务是否执行成功,使用execute方法即可
• RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
• SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
• STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
• TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
• TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。