什么是线程池
线程池是一种多线程的实现形式,处理过程中,将任务添加到队列,在创建线程后自动执行,线程池中的线程使用默认优先级(5)执行
为什么使用线程池
在实际应用开发中,我们很少直接使用Thread类来创建线程,因为大量的创建和销毁线程会带来很大的系统开销
而线程池会保留核心线程,释放不常用线程,从而达到减小系统开销的效果。
线程池的创建
Exectors类提供了创建常用线程池的简便方法,有如下几种
newSingleThreadPoolExecutor | 创建只有一个线程的线程池 |
newFixedThreadPoolExecutor | 创建固定大小的线程池,入参指定 |
newCachedThreadPoolExecutor | 创建一个大小不做限定的线程池,支持的最大线程数为Integer最大值 |
newScheduleThreadPoolExecutor | 创建周期性执行的线程池,入参指定核心线程数,最大线程数为Integer最大值 |
从源码来看,这些线程池的创建无一例外调用了ThreadPoolExecutor类的构造方法
ThreadPoolExecutor类的构造器支持7个属性
corePoolSize | 线程池核心线程数,通常核心线程不会被销毁;如果指定allowCoreThreadTimeout为true,核心线程空闲超过keepAliveTime后,会被销毁;通常不建议这么做 |
maximumPoolSize | 线程池最大线程数 |
keepAliveTime | 线程空闲多长时间后,被销毁,一般用于非核心线程 |
unit | 超时时间单位 |
workQueue | 任务等待队列;如果线程池中线程个数达到最大值,并且无空闲线程,此时提交新的任务,该任务会被缓存到等待队列,直到有线程空闲 |
threadFactory | 线程池创建新的线程使用的工厂类 |
handler |
等待队列满了之后,新提交任务的拒绝策略,线程池提供了几种常见的策略: AbortPolicy ----------- 抛出RejectedExecutionException,默认策略 DiscardPolicy -------- 直接丢弃 DiscardOldestPolicy ---- 丢弃最先等待的任务,将新提交的任务放到等待队列中 CallerRunsPolicy ------- 使用提交任务的线程执行该任务 |
建议创建线程池时使用ThreadPoolExecutor类来创建,这是因为Executors类提供的创建线程池的方法由于未指定等待队列的大小,会有OOM的风险
线程池执行的一般规则
1、提交新任务时,不管核心线程是否有空闲,创建新的线程执行任务
2、核心线程均未空闲,提交新任务时,创建新的线程,直到到达线程池最大线程数。
3、线程池中线程数达到最大线程,并且均未空闲,提交新任务时,放到等待队列中,直到有线程空闲
4、等待队列满了后,执行拒绝策略
5、非核心线程空闲超过keepAliveTime后,释放线程
6、核心线程空闲超过keepAliveTime后,不释放,除非设置allowCoreThreadTimeout为true,但是一般不建议这么做
7、拒绝策略默认为AbortPolicy,但是抛出的RejectedExecutionException为非受检异常,有时会忘记捕获,如果不关心任务执行结果,可以使用直接丢弃策略
8、对于Cache线程池,提交任务时,先看有没有空闲线程,有直接使用;没有则创建线程;线程空闲60s,则从缓存中移除
线程池任务的提交
1、无返回值的提交
2、有返回值的提交
ExecutorService类其它方法介绍
1、invokeAny()方法
方法入参为Callable类型的集合,方法返回值为其中一个执行成功任务的返回值;
有一个任务执行成功或者执行任务过程中抛出异常,会取消其余的任务的执行
示例:
public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService service = Executors.newFixedThreadPool(3); Set<Callable<String>> callableSet = new HashSet<>(); callableSet.add(new Callable<String>() { @Override public String call() throws Exception { System.out.println("Call Task1"); return "Task1"; } }); callableSet.add(new Callable<String>() { @Override public String call() throws Exception { System.out.println("Call Task2"); return "Task2"; } }); String result = service.invokeAny(callableSet); System.out.println(result); service.shutdown(); }
执行结果1:
执行结果2:
2、invokeAll()
方法入参为Callable类型的集合,方法返回值为任务执行结果Future的集合;
但是有些任务可能由于执行异常而结束,但是我们无法通过返回结果区分这一点
示例:
public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService service = Executors.newFixedThreadPool(3); Set<Callable<String>> callableSet = new HashSet<>(); callableSet.add(new Callable<String>() { @Override public String call() throws Exception { System.out.println("Call Task1"); return "Task1"; } }); callableSet.add(new Callable<String>() { @Override public String call() throws Exception { System.out.println("Call Task2"); return "Task2"; } }); List<Future<String>> futureList = service.invokeAll(callableSet); futureList.forEach(it -> { try { String result = it.get(); System.out.println(result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); service.shutdown(); }
执行结果:
线程池关闭
有个例子,如果在main方法中使用线程池提交了任务,如果未关闭线程池,那么应用程序会继续保持运行状态
线程池关闭相关的方法如下:
1、shutdown
该方法不会立即关闭线程池,而是不再接收新的任务,等到线程池中任务执行完成后,线程池真正关闭
2、shutdownNow
该方法会立即尝试结束线程池中正在执行的任务,并跳过已经提交并未执行的任务;但是正在执行的任务是否能够结束不确定
该方法的返回值为从未执行过的任务
3、awaitTermination
该方法如果在线程池收到shutdown请求后执行,会阻塞主线程,直到所有任务执行完毕或者任务执行超时亦或者任务执行异常;
返回值:如果任务执行超时,返回false;否则返回true,下面是推荐的关闭线程池的写法
service.shutdown(); if (!service.awaitTermination(80, TimeUnit.MILLISECONDS)) { service.shutdownNow(); }
4、isShutdown
返回线程池是否被关闭
5、isTerminated
返回是否所有任务在线程池关闭时全部执行完成