ExecutorService - 执行器服务
一、概述
1.线程池的意义:减少线程的创建和销毁,做到线程的重用,以提高资源的利用率
2.当线程池定义好之后,线程池中没有任何线程
3.每过来一个请求,会创建一个线程去处理这个请求,直到线程数达到指定的数量,
就不再创建,这些线程称之为核心线程
4.在核心线程数量达到指定的线程数量之前,每次的请求都会去创建一个新的核心线程
5.核心线程使用完成后,不会被销毁,而是等待下一个任务
6.如果核心线程被全部占满,后续的请求会放入线程池的工作队列中,工作队列本质上是阻塞式队列
7.如果工作队列被全部占用,则新来的请求会交给临时线程来处理
8.临时线程的数量也是有限的,临时线程用完之后不会立即销毁而是存活一段指定的时间,
如果这段时间内没有请求处理,则该临时线程才会被销毁
9.如果临时线程也全都被占满了,则后续的请求会被交给拒绝执行处理器(RejectedExecutionHandler)来进行拒绝处理
ThreadPoolExecutor:
package com.apple.pool; import java.util.concurrent.*; /** * This is Description * * @author apple * @date 2020/06/08 */ public class ExecutorServiceDemo { public static void main(String[] args) { //创建线程池 //corePoolSize - 核心线程数量 // maximumPoolSize - 最大线程数 = 核心线程数 + 临时线程数 // keepAliveTime - 临时线程存活时间 // unit - 时间单位 // workQueue - 工作队列(本质上是阻塞队列) // handler - 拒绝执行处理器 ExecutorService es = new ThreadPoolExecutor( 5, // 5个核心 10, // 5个核心 + 5个临时 5, // 存活时间 TimeUnit.SECONDS,// 时间单位 new ArrayBlockingQueue<>(5),// 工作队列 new RejectedExecutionHandler() {// 拒绝执行处理器 @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println(r + "线程被拒绝"); } }); //int n = 12; // 5个给核心,5个给工作队列,还剩下2个给临时线程,故一开始是7个线程执行,剩下2个再执行 int n = 18; // 5个给核心,5个给工作队列,还剩下8个中,5个给临时线程,还剩下3个被拒绝,故一开始10个线程执行,10个正常,3个被拒绝,剩下5个再执行 //执行线程 for (int i = 0; i < n; i++) { es.execute(new EsRunnable()); } //关闭线程池 es.shutdown(); //new Thread(new EsRunnable()).start(); } } class EsRunnable implements Runnable { @Override public void run() { System.out.println("hello ~~~"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }
线程工具类:
package com.apple.pool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * This is Description * * @author apple * @date 2020/06/08 */ public class ExecutorServiceDemo2 { public static void main(String[] args) { /** * 特点: * 1.没有核心线程,全都是临时线程 * 2.临时线程的数量为 Integer.MAX_VALUE,人为的认为这个线程池可以承载的线程数量是无限 * 3.临时线程的存活时间是 60L SECONDS - 1min * 4.工作队列是一个同步队列(线程安全) - 同步队列的容量位1 - 只能存储1个元素 * 大池子小队列 * 使用场景: * 只用于高并发短任务的场景,例如:即时通讯(发送微信,QQ聊天等) * 不适用:例如百万人同时下载一个视频,或者多人在线同时观看一个视频 */ ExecutorService es = Executors.newCachedThreadPool(); /** * 特点: *1.没有临时线程,全都是核心线程,因为 核心线程和临时线程数量一样,且临时线程存活的时间为 0L *2.工作队列是一个链式队列,默认容量是 Integer.MAX_VALUE * 人为的认为这个队列是无限的,可以缓存足够多的任务 * 大队列小池子 * 使用场景: * 不适用于高并发短任务场景,而适用于长任务的场景,例如:下载视频和文件,百度云盘 */ ExecutorService es2 = Executors.newFixedThreadPool(5); } }
二、Callable线程
1.Callable 执行完成之后会有返回结果,所以泛型限定结果类型
2.Runnable和Callable的区别:
a.返回值:Runnable没有返回值,Callable有返回值
b.启动方式:Runnable可以通过Thread启动,也可以通过线程池启动
Callable只能通过线程池启动
c.容错处理:Runnable的实现run()方法不能异常抛出,所以不能以全局方式(例如Spring中的异常通知)处理;
Callable允许以全局方式处理
package com.apple.pool; import java.util.concurrent.*; /** * This is Description * * @author apple * @date 2020/06/08 */ public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService es = Executors.newCachedThreadPool(); //既可以执行Runnable 也可以执行Callable //将返回值封装成一个Future对象 Future<String> f = es.submit(new CDDemo()); System.out.println(f.get()); } } //返回值类型是String class CDDemo implements Callable<String> { @Override public String call() throws Exception { return "SUCCESS"; } }
三、ScheduleExecutorService - 定时执行器服务
1.起到定时执行的效果
2.是很多定时器的底层实现
package com.apple.pool; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * This is Description * * @author apple * @date 2020/06/08 */ public class ScheduleExecutorServiceDemo { public static void main(String[] args) { /** * 创建线程池 */ ScheduledExecutorService ses = Executors.newScheduledThreadPool(5); /** * Callable 或 Runnable - 要执行的线程 * delay - 延时时间 * unit - 时间单位 * 推迟指定的时间再执行提交的任务 */ // ses.schedule(new ScheduleRunnable(), 5, TimeUnit.SECONDS); /** * Runnable - 要执行的线程 * initialDelay - 延时时间 * period - 间隔时间 * unit - 时间单位 * 从上一次的起始时间计时,推算下一次的起始时间 * 如果线程额执行时间大于间隔时间,则以时间执行时间为准 */ // ses.scheduleAtFixedRate(new ScheduleRunnable(), 0, 5, TimeUnit.SECONDS); /** * Runnable - 要执行的线程 * initialDelay - 延时时间 * period - 间隔时间 * unit - 时间单位 * 从上一次的结束时间,推算下一次的起始时间 */ ses.scheduleWithFixedDelay(new ScheduleRunnable(), 0, 5, TimeUnit.SECONDS); } } class ScheduleRunnable implements Runnable { @Override public void run() { System.out.println("hello ~~~"); try { //如果为3000,则 scheduleAtFixedRate()方法还是每隔5秒执行,scheduleWithFixedDelay()方法会在 间隔时间+3秒 //Thread.sleep(3000); //如果为8000,则 scheduleAtFixedRate()方法还是每隔8秒执行,scheduleWithFixedDelay()方法会在在 间隔时间+秒 Thread.sleep(8000); } catch (InterruptedException e) { e.printStackTrace(); } } }
四、ForkJoinPool - 分叉合并池
1.分叉:将任务进行分解,将一个大任务拆分成多个小的任务分配到不同的核上来执行
2.合并:将分叉的任务的执行结果进行汇总
3.分叉合并的目的:提高CPU的利用率
4.分叉合并是适用于任务量比较大的场景
5.为了防止因为慢任务导致效率降低,采用work-srealing(工作窃取)策略:即
当一个核上的任务执行完成之后,不会空闲下来而是会随机的去其他核上去“偷”一个任务去执行
package com.apple.pool; import java.util.concurrent.*; /** * This is Description * * @author apple * @date 2020/06/08 */ public class ForkAndJoinPoolDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { ForkJoinPool pool = new ForkJoinPool(); final ForkJoinTask<Long> f = pool.submit(new Sum(1, 100000000000L)); System.out.println(f.get()); } } //RecursiveTask - 有返回值 //RecursiveAction - 没有返回值 class Sum extends RecursiveTask<Long> { private long start; private long end; public Sum(long start, long end) { this.start = start; this.end = end; } @Override protected Long compute() { //每隔10000个数进行求和 //大于10000个数进行求和 if (end - start > 10000) { long mid = (start + end) / 2; Sum left = new Sum(start, mid); Sum right = new Sum(start + 1, end); //分叉 left.fork(); right.fork(); //合并 return left.join() + right.join(); } else { //如果小于10000个数,就进行求和 long sum = 0; for (long i = start; i <= end; i++) { sum += i; } return sum; } } }