本文源码:GitHub·点这里 || GitEE·点这里
一、Executor框架简介
1、基础简介
Executor系统中,将线程任务提交和任务执行进行了解耦的设计,Executor有各种功能强大的实现类,提供便捷方式来提交任务并且获取任务执行结果,封装了任务执行的过程,不再需要Thread().start()方式,显式创建线程并关联执行任务。
2、调度模型
线程被一对一映射为服务所在操作系统线程,启动时会创建一个操作系统线程;当该线程终止时,这个操作系统线程也会被回收。
3、核心API结构
Executor框架包含的核心接口和主要的实现类如下图所示:
线程池任务:核心接口:Runnable、Callable接口和接口实现类;
任务的结果:接口Future和实现类FutureTask;
任务的执行:核心接口Executor和ExecutorService接口。在Executor框架中有两个核心类实现了ExecutorService接口,ThreadPoolExecutor和ScheduledThreadPoolExecutor。
二、用法案例
1、API基础
ThreadPoolExecutor基础构造
1public ThreadPoolExecutor(int corePoolSize, 2 int maximumPoolSize, 3 long keepAliveTime, 4 TimeUnit unit, 5 BlockingQueue<Runnable> workQueue, 6 ThreadFactory threadFactory, 7 RejectedExecutionHandler handler) {}
参数名
说明
corePoolSize
线程池的核心大小,队列没满时,线程最大并发数
maximumPoolSize
最大线程池大小,队列满后线程能够容忍的最大并发数
keepAliveTime
空闲线程等待回收的时间限制
unit
keepAliveTime时间单位
workQueue
阻塞的队列类型
threadFactory
创建线程的工厂,一般用默认即可
handler
超出工作队列和线程池时,任务会默认抛出异常
2、初始化方法
1ExecutorService :Executors.newFixedThreadPool(); 2ExecutorService :Executors.newSingleThreadExecutor(); 3ExecutorService :Executors.newCachedThreadPool(); 4 5ThreadPoolExecutor :new ThreadPoolExecutor() ;
通常情况下,线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险。
3、基础案例
1package com.multy.thread.block08executor; 2import java.util.concurrent.*; 3 4public class Executor01 { 5 // 定义线程池 6 private static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor( 7 3,10,5000,TimeUnit.SECONDS, 8 new SynchronousQueue<>(),Executors.defaultThreadFactory(),new ExeHandler()); 9 public static void main(String[] args) { 10 for (int i = 0 ; i < 100 ; i++){ 11 poolExecutor.execute(new PoolTask(i)); 12 //带返回值:poolExecutor.submit(new PoolTask(i)); 13 } 14 } 15} 16// 定义线程池任务 17class PoolTask implements Runnable { 18 19 private int numParam; 20 21 public PoolTask (int numParam) { 22 this.numParam = numParam; 23 } 24 @Override 25 public void run() { 26 try { 27 System.out.println("PoolTask "+ numParam+" begin..."); 28 Thread.sleep(5000); 29 } catch (Exception e) { 30 e.printStackTrace(); 31 } 32 } 33 public int getNumParam() { 34 return numParam; 35 } 36 public void setNumParam(int numParam) { 37 this.numParam = numParam; 38 } 39} 40// 定义异常处理 41class ExeHandler implements RejectedExecutionHandler { 42 @Override 43 public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) { 44 System.out.println("ExeHandler "+executor.getCorePoolSize()); 45 executor.shutdown(); 46 } 47}
流程分析
- 线程池中线程数小于corePoolSize时,新任务将创建一个新线程执行任务,不论此时线程池中存在空闲线程;
- 线程池中线程数达到corePoolSize时,新任务将被放入workQueue中,等待线程池中任务调度执行;
- 当workQueue已满,且maximumPoolSize>corePoolSize时,新任务会创建新线程执行任务;
- 当workQueue已满,且提交任务数超过maximumPoolSize,任务由RejectedExecutionHandler处理;
- 当线程池中线程数超过corePoolSize,且超过这部分的空闲时间达到keepAliveTime时,回收该线程;
- 如果设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收;
三、线程池应用
应用场景:批量账户和密码的校验任务,在实际的业务中算比较常见的,通过初始化线程池,把任务提交执行,最后拿到处理结果,这就是线程池使用的核心思想:节省资源提升效率。
1public class Executor02 { 2 3 public static void main(String[] args) { 4 // 初始化校验任务 5 List<CheckTask> checkTaskList = new ArrayList<>() ; 6 initList(checkTaskList); 7 // 定义线程池 8 ExecutorService executorService ; 9 if (checkTaskList.size() < 10){ 10 executorService = Executors.newFixedThreadPool(checkTaskList.size()); 11 }else{ 12 executorService = Executors.newFixedThreadPool(10); 13 } 14 // 批量处理 15 List<Future<Boolean>> results = new ArrayList<>() ; 16 try { 17 results = executorService.invokeAll(checkTaskList); 18 } catch (InterruptedException e) { 19 Thread.currentThread().interrupt(); 20 } 21 // 查看结果 22 for (Future<Boolean> result : results){ 23 try { 24 System.out.println(result.get()); 25 // System.out.println(result.get(10000,TimeUnit.SECONDS)); 26 } catch (Exception e) { 27 e.printStackTrace() ; 28 } 29 } 30 // 关闭线程池 31 executorService.shutdownNow(); 32 } 33 34 private static void initList (List<CheckTask> checkTaskList){ 35 checkTaskList.add(new CheckTask("root","123")) ; 36 checkTaskList.add(new CheckTask("root1","1234")) ; 37 checkTaskList.add(new CheckTask("root2","1235")) ; 38 } 39} 40// 校验任务 41class CheckTask implements Callable<Boolean> { 42 private String userName ; 43 private String passWord ; 44 public CheckTask(String userName, String passWord) { 45 this.userName = userName; 46 this.passWord = passWord; 47 } 48 @Override 49 public Boolean call() throws Exception { 50 // 校验账户+密码 51 if (userName.equals("root") && passWord.equals("123")){ 52 return Boolean.TRUE ; 53 } 54 return Boolean.FALSE ; 55 } 56}
线程池主要用来解决线程生命周期开销问题和资源不足问题,通过线程池对多个任务线程重复使用,线程创建也被分摊到多个任务上,多数任务提交就有空闲的线程可以使用,所以消除线程频繁创建带来的开销。
四、源代码地址
1GitHub·地址 2https://github.com/cicadasmile/java-base-parent 3GitEE·地址 4https://gitee.com/cicadasmile/java-base-parent
