Current并发包(三) - ExecutorService
ExecutorService概述
ExecutorService本质上是一个线程池
线程池的意义:做到线程的复用,减少线程的创建和销毁
当线程创建好后,里面暂时没有任何线程,此时这个池子是空的
每过来一个请求的时候,就会在线程池中创建一个线程(核心线程)来处理
核心线程在使用完成之后不会被销毁
在核心线程达到指定的数量之前,即使有核心线程空闲,新来的请求依然会创建一个核心线程来处理,直到核心线程达到指定的数量
如果所有的核心线程都被占用,则新来的请求会被放入工作队列中。工作队列本质上是一个阻塞式队列
如果工作队列也被占满,则新来的请求会被交给临时线程来处理
如果临时线程被全部占用,再来的请求会交给RejectedExcutionHandler来进行拒绝
ExecutorServiceDemo.java
package com.wiscom.pool; public class ExecutorServiceDemo { public static void main(String[] args) { // ExecutorService es = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue) // 创建一个执行器服务 // corePoolSize - 核心线程数量 // maximumPoolSize - 最大线程数量 = 核心线程数 + 临时线程数 // keepAliveTime - 临时线程存活时间 // unit - 时间单位 // workQueue - 工作队列 // handler - 拒绝执行处理器 ExecutorService es = new ThreadPoolExecutor(5, // 5个核心线程 10, // 5个核心+5个临时 5, TimeUnit.SECONDS, // 临时线程在使用完成之后要存活5s new ArrayBlockingQueue<Runnable>(5), // 工作队列中能够存储5个请求 new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("拒绝执行" + r); } }); // 把任务提交给线程池来执行 for (int i = 0; i < 18; i++) es.execute(new ESRunnable()); // 关闭线程池 es.shutdown(); } } class ESRunnable implements Runnable { @Override public void run() { try { System.out.println("hello~~~"); Thread.sleep(3000); System.out.println("finished~~~"); } catch (InterruptedException e) { e.printStackTrace(); } } }
Callable
Callbale是JDK1.5提供的一个定义线程的类
Callable和Runnable的区别
返回值
Runnable没有返回值
Callable允许返回效果
启动方式
Runnable可以通过Thread或者线程来启动
Callable只能通过线程池来启动
容错机制
Runnable不允许抛出异常,导致无法利用全局方式(如:Spring中的异常通知)来处理异常
Callable允许抛出异常,可以利用全局方式进行处理
ExecutorServiceDemo2.java
package com.wiscom.pool; public class ExecutorServiceDemo2 { public static void main(String[] args) throws InterruptedException, ExecutionException { // 特点: // 1. 没有核心线程全部都是临时线程 // 2. 临时线程的数量是Integer.MAX_VALUE。考虑实际开发中 // 单台服务器能处理的线程数量是有限的 // 导致可以认为这个线程池能够处理无限多的请求 // 3. 临时线程的存活时间是1min // 4. 工作队列是一个同步队列,只能存储1个元素 // 大池子小队列 // 适应场景:适用于短任务高并发场景 // ExecutorService es = Executors.newCachedThreadPool(); // 特点: // 1. 没有临时线程全部都是核心线程 // 2. 工作队列是一个LinkedBlockingQueue // 并且这个队列在创建的时候没有容量 // 那么意味着这个队列的容量是Integer.MAX_VALUE // 可以认为这个队列的容量是无限的 // 也就意味着这个线程池可以存储无限多的任务 // 小池子大队列 // 适应场景:适用于长任务场景 - 百度云盘下载 ExecutorService es = Executors.newFixedThreadPool(5); // Callable线程不能通过Thread启动,只能通过线程池启动 // submit既可以执行Runnable也可以执行Callable Future<String> f = es.submit(new CallableDemo()); // 关闭线程池 es.shutdown(); // 从Future中将实际结果来解析出来 System.out.println(f.get()); } } // 泛型规定的是返回结果的类型 class CallableDemo implements Callable<String> { @Override public String call() throws Exception { return "SUCCESS"; } }
ScheduledExecutorService - 定时执行器
这个类能提供定时的效果
ScheduledExecutorServiceDemo.java
package com.wiscom.pool; public class ScheduledExecutorServiceDemo { public static void main(String[] args) { ScheduledExecutorService ses = Executors.newScheduledThreadPool(5); // 这个方法能起到定时的效果 // command - 提交的任务 // delay - 延时时间 // unit - 时间单位 // 延迟5s再执行提交的任务 // ses.schedule(new ScheduledRunnable(), 5, TimeUnit.SECONDS); // 每隔5s执行一次这个任务 // 从上一次的开始来计算下一次的启动时间 // 如果线程执行时间大于间隔时间 // 则两次任务的间隔时间以线程执行时间为准 ses.scheduleAtFixedRate( new ScheduledRunnable(), 0, 5, TimeUnit.SECONDS); // 从上一次的结束来计算下一次的启动时间 ses.scheduleWithFixedDelay( new ScheduledRunnable(), 0, 5, TimeUnit.SECONDS); } } class ScheduledRunnable implements Runnable { @Override public void run() { try { System.out.println("running~~~"); Thread.sleep(8000); System.out.println("finished~~~"); } catch (InterruptedException e) { e.printStackTrace(); } } }
ForkJoinPool
分叉
将一个大的任务拆分成多个小任务交给多个线程来执行
合并
将小任务的执行结果进行汇总
分叉合并的目的
提高cpu的利用率,提高效率
如果数据量较大,则适合使用分叉合并
如果数据量较小,此时循环的效率反而更高
分叉合并过程
分叉合并在给每一个核分配任务的时候,尽量保证每一个核的任务数量基本相等。
在分叉合并中,为了防止“慢任务”导致整体的效率降低,所以采取了work-stealing(工作窃取)策略来解决这个问题。
工作窃取策略
是指当前核上的任务执行完成后,这个核并不会空闲下来,而是继续扫面其他的核,随机的从一个核的任务队列的尾端 偷 一个任务回来执行
ForkJoinPoolDemo.java
package com.wiscom.pool; public class ForkJoinPoolDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { long begin = System.currentTimeMillis(); long sum = 0; for (long i = 0; i <= 100000000000L; i++) { sum += i; } System.out.println(sum); // 创建分叉合并池 // ForkJoinPool pool = new ForkJoinPool(); // Future<Long> f = pool.submit(new Sum(1, 100000000000L)); // System.out.println(f.get()); // pool.shutdown(); long end = System.currentTimeMillis(); System.out.println(end - begin); } } @SuppressWarnings("serial") class Sum extends RecursiveTask<Long> { private long start; private long end; public Sum(long start, long end) { this.start = start; this.end = end; } @Override public Long compute() { if (end - start <= 10000) { long sum = 0; for (long i = start; i <= end; i++) { sum += i; } return sum; } else { long mid = (start + end) / 2; Sum left = new Sum(start, mid); Sum right = new Sum(mid + 1, end); // 分叉 left.fork(); right.fork(); // 合并 return left.join() + right.join(); } } }