JAVA篇:Java 多线程 (七)线程池-Executor相关类
7 线程池-Executor相关类
关键字:Callable、Future和FutureTask,ForkJoinPool、ThreadPoolExcutor、ScheduledThreadPoolExcutor,CompletionService,Exectors(工厂类)
7.1 Executor框架
7.1.1 线程池概念
前面在了解ThreadGroup的时候有提到过线程池的概念。Executor相关类就提供了构建线程池的方法。
若我们使用new Thread的方法创建线程、通过数组、ThreadGroup的方法管理线程,当系统频繁调用线程时,线程需要频繁创建,线程创建带来了很大的开销,而且线程数量无法控制,过多线程对程序和系统来说也并不是好事。
而线程池的作用就是线程的创建统一由线程池控制,当有任务需要线程调度时则向线程池申请,当任务运行完毕后将线程放回线程池供其他任务使用。线程池可以控制系统中使用的线程数量,也能复用线程,避免频繁创建销毁线程带来的额外开销。
7.1.2 Executor框架下主要的接口和类
在图上我们可以看到
-
线程池框架的顶级接口Exector、提供了线程生命周期的管理方法的接口ExectorService、增加了延时和定期执行任务功能的接口ScheduleExcutorService。
-
实现类ForkJoinPool、ThreadPoolExcutor、ScheduledThreadPoolExcutor
-
以及其他的Exectors(工厂类)、CompletionService接口、Callable、Future和FutureTask
7.2 Callable、Future和FutureTask
之前有看到过除了Runnable和Thread创建线程的方法,还存在一个方法,即是Callable、Future和FutureTask。这是一个有返回值的线程。这种有返回值的线程,最简单的应用,例如,一个程序的运算包含步骤1(运算约10分钟)和步骤2(运算约20分钟),步骤2部分运算依赖于步骤1返回的结果,那么可以异步运行步骤1,步骤2运算完不需要依赖于步骤1结果的部分后,再等待步骤1的返回值。
当然,之前也有了解过线程的等待阻塞和通过操作共享变量进行线程间通信,似乎通过这些方法也能实现上面说的过程。但是Callable、Future和FutureTask提供了一个更加方便的机制。
这三个接口及类的知识点相对独立,也可以与Executor下的相关类配合使用,所以先将这部分的了解放在这里,同时也对线程池的应用有一些基本的了解。
这三个接口或实现类定义在java.util.concurrent下。
7.2.1 Callable<T>接口
Callable<T>是返回结果并可能引发异常的任务。
Callable接口类似于Runnable,因为它们都是为其实例可能由另一个线程执行的类而设计的。然而Runnable不返回结果,也不能抛出被检查的异常。
Callable定义了一个需要重写的方法。
/** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception;
7.2.2 Future<T>接口
Future是一个接口表示异步计算的结果,它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
Future定义了以下几种方法:
-
boolean cancel(boolean mayInterruptIfRunning) :尝试取消执行此任务。
-
V get() :等待计算完成,然后检索其结果。
-
V get(long timeout, TimeUnit unit) :如果需要等待最多在给定的时间计算完成,然后检索其结果(如果可用)。
-
boolean isCancelled() :如果此任务在正常完成之前被取消,则返回 true 。
-
boolean isDone() :返回 true如果任务已完成。
7.2.3 RunnableFuture<V> 接口
RunnableFuture<V> 接口同时继承了Runnable和Future<T>,是FutureTask<T>的直接父类。
7.2.4 FutureTask<T>
FutureTask是Future的实现类,它提供了对Future的基本实现。可使用FutureTask包装Callable或Runnable对象,因为FutureTask实现Runnable,所以也可以将FutureTask提交给Executor。
7.2.5 Callable、Future和FutureTask的简单应用
如果要使用线程运行并返回结果的话,单单Callable、Future和FutureTask这三者是不行的,因为即使是FutureTask也只是实现了Runnable,仍需要依赖其他的实例,譬如Thread类的实例来运行线程。
Callable、Future、FutureTask一般都是和线程池配合使用的,因为线程池ThreadPoolExecutor的父类AbstractExecutorService提供了三种submit方法,其中方法1和方法3与三者相关。
-
<T> Future<T> submit(Callable<T> task) :提交值返回任务以执行,并返回代表任务待处理结果的Future。
-
Future<?> submit(Runnable task) :提交一个可运行的任务执行,并返回一个表示该任务的未来。
-
<T> Future<T> submit(Runnable task, T result) :提交一个可运行的任务执行,并返回一个表示该任务的未来。
先实现了Callable<T>,自定义实现类内部运行的逻辑。
/* 实现callable */ class MyCall implements Callable<String>{ @Override public String call() throws Exception { System.out.println(Thread.currentThread().getName()+": 进入call,开始休眠......"); Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+": 结束休眠,退出call,返回结果。"); return "call_finish!"; } }
7.2.6 Callable+FutureTask+Thread
只是将FutureTask当成一个封装了返回结果的Runnable。
/* callable+futureTask+Thread */ public void test1(){ Callable<String> callable = new MyCall(); FutureTask<String> futureTask = new FutureTask<>(callable); new Thread(futureTask).start();//由于FutureTask只是实现了Runnable,其实并没有方法创建并运行线程,最简单的是使用Thread相关方法 System.out.println(Thread.currentThread().getName()+": 进行运算......"); System.out.println(Thread.currentThread().getName()+": 等待call......"); try { System.out.println(Thread.currentThread().getName()+": 获得结果--"+futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+": 运行结束。"); }
结果:
main: 进行运算......
main: 等待call......
Thread-0: 进入call,开始休眠......
Thread-0: 结束休眠,退出call,返回结果。
main: 获得结果--call_finish!
main: 运行结束。
7.2.7 Callable+FutureTask+工厂类Executors创建的线程池
这里使用Executors返回线程池,对FutureTask任务进行运算。
/* callable+futureTask+工厂类executors创建的线程池 */ public void test2(){ Callable<String> callable = new MyCall(); FutureTask<String> futureTask = new FutureTask<>(callable); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.submit(futureTask); executorService.shutdown(); System.out.println(Thread.currentThread().getName()+": 进行运算......"); System.out.println(Thread.currentThread().getName()+": 等待call......"); try { System.out.println(Thread.currentThread().getName()+": 获得结果--"+futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+": 运行结束。"); }
结果:
pool-1-thread-1: 进入call,开始休眠......
main: 进行运算......
main: 等待call......
pool-1-thread-1: 结束休眠,退出call,返回结果。
main: 获得结果--call_finish!
main: 运行结束。
7.2.8 Callable+Future+工厂类Executors创建的线程池
线程池还提供了方法<T> Future<T> submit(Callable<T> task)
,可直接调用Callable。
/* callable+future+工厂类executors创建的线程池 */ public void test3(){ Callable<String> callable = new MyCall(); ExecutorService executorService = Executors.newCachedThreadPool(); Future<String> future = executorService.submit(callable); executorService.shutdown(); System.out.println(Thread.currentThread().getName()+": 进行运算......"); System.out.println(Thread.currentThread().getName()+": 等待call......"); try { System.out.println(Thread.currentThread().getName()+": 获得结果--"+future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+": 运行结束。"); }
结果:
pool-1-thread-1: 进入call,开始休眠......
main: 进行运算......
main: 等待call......
pool-1-thread-1: 结束休眠,退出call,返回结果。
main: 获得结果--call_finish!
main: 运行结束。
7.3 ThreadPoolExecutor线程池
先讲ThreadPoolExecutor线程池及相关接口:
-
Exector接口:线程池的顶级接口。
-
ExectorService接口:Exector接口的子接口,提供了生命周期的管理方法,以及定义了任务提交返回Future对象的方法。
-
AbstractExecutorService抽象类:继承了ExectorService并实现了部分方法,是ThreadPoolExecutor及ForkJoinPool的直接子类。
-
ThreadPoolExecutor线程池:提供了线程池的构造及操作方法,不过一般通过工厂类Excutors来配置
7.3.1 Exector接口
Exector接口是线程池的顶级接口,仅仅定义了一个方法void execute(Runnable command)
。该方法在将来的某个时间执行给定的命令。 该命令可以在一个新线程,一个合并的线程中或在调用线程中执行,由Executor实现。
Exector提供了一种将任务提交从每个任务的运行机制分解的方式,包括线程使用,调度等的Executor 。通常使用Executor而不是显式创建线程。
该包中提供的ExecutorService实现了Executor ,这是一个更广泛的接口。 ThreadPoolExecutor类提供了一个可扩展的线程池实现。 Executors类为这些执行人员提供了方便的工厂方法。
7.3.2 ExectorService接口
是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,返回 Future 对象,以及可跟踪一个或多个异步任务执行状况返回Future的方法。
Modifier and Type | Method and Description |
---|---|
boolean |
awaitTermination(long timeout, TimeUnit unit) 阻塞当前线程,直至以下三种情况之一发生:1、所有任务运行完毕并返回。2、超时。3、被中断。 |
<T> List<Future<T>> |
invokeAll(Collection<? extends Callable<T>> tasks) 执行给定的任务,直到所有任务完成,返回全部的运行状态和结果。 |
<T> List<Future<T>> |
invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) 执行给定的任务,直到所有任务完成或者超时时,返回全部任务的运行状态和结果。 |
<T> T |
invokeAny(Collection<? extends Callable<T>> tasks) 执行给定的任务,返回一个成功完成的结果(即没有抛出异常),如果有的话。 |
<T> T |
invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) 执行给定的任务,返回一个已经成功完成的结果(即,不抛出异常),如果有的话在给定的超时之前过去。 |
boolean |
isShutdown() 如果此执行者已关闭,则返回 true 。 |
boolean |
isTerminated() 如果所有任务在关闭后完成,则返回 true 。 |
void |
shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。 |
List<Runnable> |
shutdownNow() 尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表。 |
<T> Future<T> |
submit(Callable<T> task) 提交值返回任务以执行,并返回代表任务待处理结果的Future。 |
Future<?> |
submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。 |
<T> Future<T> |
submit(Runnable task, T result) 提交一个可运行的任务执行,并返回一个表示该任务的未来。 |
7.3.3 AbstractExecutorService
AbstractExecutorService是一个抽象类,它是 JDK 线程池 ThreadPoolExecutor,ForkJoinPool 等的父类,实现了ExecutorService接口。
AbstractExecutorService实现了 ExecutorService 定义的执行任务的方法,比如 submit,invokeAll,invokeAny 等。AbstractExecutorService 提供了一个 newTaskFor 方法用于构建 RunnableFuture 对象。执行任务方法返回的跟踪任务执行结果的对象都是通过 newTaskFor 来构建的。如果有需要可以通过自定义 newTaskFor 来构建所需的 RunnableFuture。
//返回给定可调用任务的 RunnableFuture 。 protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) //对于给定的可运行和默认值,返回一个 RunnableFuture 。 protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value)
7.3.4 ThreadPoolExecutor线程池
ThreadPoolExecutor使用池中几个线程来运行每个提交的任务。
这里对ThreadPoolExecutor进行简单的了解。
ThreadPoolExecutor提供了四个构造函数,其中包含的参数如下:
-
corePoolSize和maximumPoolSize: 线程池中核心线程数和最大线程数。当workQueue为无界队列时,maximumPoolSize不起作用(超过核心线程数后任务全部会缓存到队列中,不会创建非核心线程)。线程池内部处理流程如下:
-
keepAliveTime和unit: 非核心空闲线程的存活时间及单位。keepAliveTime只对超出corePoolSize的线程起作用,这些非核心线程空闲时间超过keepAliveTime则会销毁。如果要设置核心线程的空闲超时时间,使用allowCoreThreadTimeOut(boolean)可将空闲超时策略应用于核心线程,但是前提是超时时间不为0.
-
workQueue: 用于缓存任务的阻塞队列(BlockingQueue),前面有讨论过BlockingQueue的实现类包含:ArrayBlockingQueue(有界阻塞队列)、LinkedBlockingQueue(无界阻塞队列)、SynchronousQueue(单进单出阻塞队列)、PriorityBlockingQueue(优先级阻塞队列)、DelayQueue(延时优先级队列)。
-
threadFactory: 创建线程的工厂,默认使用Executors.defaultThreadFactory()为线程池工厂。
-
handler: 表示当workQueue已满,且池中线程数达到maximumPoolSize时,线程池拒绝添加新任务时采取的策略,默认为AbortPolicy。也可实现RejectedExecutionHandler接口来自定义拒绝策略。常用拒绝策略:
-
AbortPolicy:直接抛异常;
-
CallerRunsPolicy:用调用者所在线程来运行任务;
-
DiscardOldestPolicy:丢弃队列最近的任务,并执行当前任务;
-
DiscardPolicy:不处理,丢弃(但不抛出异常)。
-
Constructor and Description |
---|
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) 创建一个新的 ThreadPoolExecutor 与给定的初始参数和默认线程工厂和拒绝执行处理程序。 |
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) 创建一个新的 ThreadPoolExecutor 与给定的初始参数和默认线程工厂。 |
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) 创建一个新的 ThreadPoolExecutor 与给定的初始参数和默认拒绝执行处理程序。 |
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 创建一个新 ThreadPoolExecutor 给定的初始参数。 |
以下是ThreadPoolExecutor的一些方法:
Modifier and Type | Method and Description |
---|---|
protected void |
afterExecute(Runnable r, Throwable t) 完成指定Runnable的执行后调用方法。 |
void |
allowCoreThreadTimeOut(boolean value) 设置策略是否核心线程可能会超时,如果任务没有在活着的时间内到达,则在新任务到达时被替换。 |
boolean |
allowsCoreThreadTimeOut() 如果此池允许核心线程超时并终止,如果没有任务在keepAlive时间内到达,则返回true,如果新任务到达时需要更换。 |
boolean |
awaitTermination(long timeout, TimeUnit unit) 阻止所有任务在关闭请求完成后执行,或发生超时,或当前线程中断,以先到者为准。 |
protected void |
beforeExecute(Thread t, Runnable r) 在给定的线程中执行给定的Runnable之前调用方法。 |
void |
execute(Runnable command) 在将来某个时候执行给定的任务。 |
protected void |
finalize() 当这个执行器不再被引用并且没有线程时,调用 shutdown 。 |
int |
getActiveCount() 返回正在执行任务的线程的大概数量。 |
long |
getCompletedTaskCount() 返回完成执行的任务的大致总数。 |
int |
getCorePoolSize() 返回核心线程数。 |
long |
getKeepAliveTime(TimeUnit unit) 返回线程保持活动时间,这是超过核心池大小的线程在终止之前可能保持空闲的时间量。 |
int |
getLargestPoolSize() 返回在池中同时进行的最大线程数。 |
int |
getMaximumPoolSize() 返回允许的最大线程数。 |
int |
getPoolSize() 返回池中当前的线程数。 |
BlockingQueue<Runnable> |
getQueue() 返回此执行程序使用的任务队列。 |
RejectedExecutionHandler |
getRejectedExecutionHandler() 返回不可执行任务的当前处理程序。 |
long |
getTaskCount() 返回计划执行的任务的大概总数。 |
ThreadFactory |
getThreadFactory() 返回用于创建新线程的线程工厂。 |
boolean |
isShutdown() 如果此执行者已关闭,则返回 true 。 |
boolean |
isTerminated() 如果所有任务在关闭后完成,则返回 true 。 |
boolean |
isTerminating() 如果此执行者在 |
int |
prestartAllCoreThreads() 启动所有核心线程,导致他们等待工作。 |
boolean |
prestartCoreThread() 启动核心线程,使其无法等待工作。 |
void |
purge() 尝试从工作队列中删除已取消的所有 |
boolean |
remove(Runnable task) 如果此任务存在,则从执行程序的内部队列中删除此任务,从而导致该任务尚未运行。 |
void |
setCorePoolSize(int corePoolSize) 设置核心线程数。 |
void |
setKeepAliveTime(long time, TimeUnit unit) 设置线程在终止之前可能保持空闲的时间限制。 |
void |
setMaximumPoolSize(int maximumPoolSize) 设置允许的最大线程数。 |
void |
setRejectedExecutionHandler(RejectedExecutionHandler handler) 为不可执行的任务设置一个新的处理程序。 |
void |
setThreadFactory(ThreadFactory threadFactory) 设置用于创建新线程的线程工厂。 |
void |
shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。 |
List<Runnable> |
shutdownNow() 尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表。 |
protected void |
terminated() 执行程序已终止时调用方法。 |
String |
toString() 返回标识此池的字符串及其状态,包括运行状态和估计的工作人员和任务计数的指示。 |
7.3.5 ThreadPoolExecutor简单应用
/* 自定义的拒绝策略 */ /* 直接放弃任务并打印提示 */ class MyHandle implements RejectedExecutionHandler{ @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { /*try { executor.getQueue().put(r); } catch (InterruptedException e) { System.out.println(e); }*/ System.out.println(Thread.currentThread().getName()+": 丢弃任务"+r); } } /* 自定义任务 */ class MyTask implements Runnable{ private int num; public MyTask(int num){ this.num = num; } @Override public void run(){ System.out.println(Thread.currentThread().getName()+":MyTask"+num+"开始运行......."); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":MyTask"+num+"完成任务"); } @Override public String toString(){ return "MyTask"+num; } } public void test(){ ArrayBlockingQueue<Runnable> workqueue = new ArrayBlockingQueue<>(3); ThreadPoolExecutor pool = new ThreadPoolExecutor( 5, //核心线程数 5, //最大线程数 300, //当核心线程数=最大线程数,设置的存活时间无效 TimeUnit.MILLISECONDS, workqueue, //缓存任务的队列 //new ThreadPoolExecutor.CallerRunsPolicy()//拒绝策略:使用调用线程完成任务 new MyHandle()//自定义拒绝策略:打印提示后,直接丢弃 ); /* 创建任务运行 */ int tasknum=10; for(int i=0;i<tasknum;i++){ System.out.println(Thread.currentThread().getName()+": 现在活跃线程数"+pool.getActiveCount()+",添加新任务"+i); pool.execute(new MyTask(i)); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } pool.shutdown();//拒绝接受新任务 }
7.4 ForkJoinPool
接下来关注AbstractExecutorService的另外一个直接子类ForkJoinPool,大部分参考的:
7.4.1 Fork/Join 分治算法思想
在必要的情况下,将一个大任务进行拆分(fork)成若干个子任务(拆到不能再拆,这里就是指我们指定的拆分的临界值),再将一个个小任务的记过进行join汇总。
7.4.2 ForkJoinPool线程池
在java 7 中引入了新的线程池ForkJoinPool,它使用了一个无限队列来保存需要执行的任务,而线程的数量则是通过构造函数传入,如果没有向构造函数中传入希望的线程数量,那么当前计算机可用的CPU数量会被设置为线程数量作为默认值。
ForkJoinPool主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题。典型的应用比如快速排序算法。
这里的要点在于,ForkJoinPool需要使用相对少的线程(默认系统自带cpu核数)来处理大量的任务。
比如要对1000万个数据进行排序,那么会将这个任务分割成两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停止分割,转而使用插入排序对它们进行排序。
那么到最后,所有的任务加起来会有大概2000000+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。
所以当使用ThreadPoolExecutor时,使用分治法会存在问题,因为ThreadPoolExecutor中的线程无法像任务队列中再添加一个任务并且在等待该任务完成之后再继续执行。而使用ForkJoinPool时,就能够让其中的线程创建新的任务,并挂起当前的任务,此时线程就能够从队列中选择子任务执行。
以上程序的关键是fork()和join()方法。在ForkJoinPool使用的线程中,会使用一个内部队列来对需要执行的任务以及子任务进行操作来保证它们的执行顺序。
ps:ForkJoinPool在执行过程中,会创建大量的子任务,导致GC进行垃圾回收,这些是需要注意的。
7.4.3 ForkJoinPool 相关的一些类
-
ForkJoinTask
ForkJoinTask<V>代表一个可以并行、合并的任务,是一个继承了Future<V>的抽象类,它还有两个抽象子类:RecusiveActive和RecusiveTask。
其中,RecusiveActive代表有返回值的任务,而RecusiveTask代表没有返回值的任务。
ForkJoinTask需要通过ForkJoinPool来执行。
ForkJoinTask可以理解为类线程但比线程轻量的实体,在ForkJoinPool中运行的少量ForkJoinWorkerThread可以持有大量的ForkJoinTask和它的子任务。
ForkJoinTask同时也是一个轻量的Future,使用时应避免较长阻塞和IO。
-
ForkJoinWorkerThread
ForkJoinWorkerThread线程是一种在Fork/join框架中运行的特性线程,它除了具有普通线程的特性外,最主要的特点是每一个ForkJoinWorkerThread线程都具有一个独立的任务等待队列(ForkJoinPool.workqueue),这个队列用于存储在本线程中被拆分的若干子任务。
-
ForkJoinPool
ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread 数组组成。ForkJoinTask数组负责将存放程序提交给ForkJoinPool,而ForkJoinWorkerThread负责执行这些任务。
任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。
当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务(工作窃取算法)。
也就是说Fork/Join采用“工作窃取模式”,当执行新的任务时他可以将其拆分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随即线程中偷一个并把它加入自己的队列中。
就比如两个CPU上有不同的任务,这时候A已经执行完,B还有任务等待执行,这时候A就会将B队尾的任务偷过来,加入自己的队列中,对于传统的线程,ForkJoin更有效的利用的CPU资源!
7.4.4 ForkJoinPool 简单应用
下面的代码结果是
从1到1000000000进行累加,结果应当是:500000000500000000 使用单线程的顺序相加,结果是500000000500000000,用时278 使用单线程的归并加法,结果是500000000500000000,用时1061 可用线程数:8 使用Fork/join ,结果是500000000500000000,用时3777
与预计的正好相反,大概因为累加本身就不是很复杂的操作,采用一些花里胡哨的操作,反而会增加如线程上下文切换而导致的花费。但是ForkJoinPool 依旧是一个有用的实现类,或许有机会深入了解一下应用的场景。
public long THRESHOLD = 10;//阈值,当剩余的数值小于该数,直接顺序相加 public long sequenceSum(long start,long end){ long sum = 0; for(long i=start;i<=end;i++){ sum+=i; } return sum; } /* 使用单线程分治方法求和 */ public long getSum(long start,long end){ long length = end-start; if(length<THRESHOLD){ return sequenceSum(start,end); } long left = getSum(start,start+length/2); long right = getSum(start+length/2+1,end); return left+right; } /* 使用 */ class ForkJoinSum extends RecursiveTask<Long>{ private long start; private long end; public ForkJoinSum(long start,long end){ this.start=start; this.end=end; } @Override protected Long compute(){ long length = end-start; if(length<THRESHOLD){ return sequenceSum(start,end); } ForkJoinSum left = new ForkJoinSum(start,start+length/2); left.fork();//拆分并压入栈顶 ForkJoinSum right = new ForkJoinSum(start+length/2+1,end); right.fork();//拆分并压入栈顶 return left.join()+right.join(); } } public void test1(){ long start = 1; long end = 1_000_000_000; System.out.println(String.format("从%d到%d进行累加,结果应当是:%d",start,end,(end+start)*(end-start+1)/2)); long start_time = System.currentTimeMillis(); /* 使用顺序相加 */ long res = sequenceSum(start,end); System.out.println(String.format("使用单线程的顺序相加,结果是%d,用时%d",res,System.currentTimeMillis()-start_time)); /* 使用单线程的归并加法*/ start_time = System.currentTimeMillis(); res = getSum(start,end); System.out.println(String.format("使用单线程的归并加法,结果是%d,用时%d",res,System.currentTimeMillis()-start_time)); /* 使用Fork/join */ System.out.println("可用线程数:"+Runtime.getRuntime().availableProcessors()); start_time = System.currentTimeMillis(); ForkJoinPool pool = new ForkJoinPool(); ForkJoinSum forkJoinSum = new ForkJoinSum(start,end); res = pool.invoke(forkJoinSum); System.out.println(String.format("使用Fork/join ,结果是%d,用时%d",res,System.currentTimeMillis()-start_time)); }
7.5 ScheduledThreadPoolExcutor
接下来时可以延时或者定时执行任务的线程池ScheduledThreadPoolExcutor。
7.5.1 ScheduleExcutorService接口
ScheduleExcutorService继承了ExectorService接口,提供了延时和定期执行任务的功能。
Modifier and Type | Method and Description |
---|---|
<V> ScheduledFuture<V> |
schedule(Callable<V> callable, long delay, TimeUnit unit) 创建并执行在给定延迟后启用的ScheduledFuture。 |
ScheduledFuture<?> |
schedule(Runnable command, long delay, TimeUnit unit) 创建并执行在给定延迟后启用的单次操作。 |
ScheduledFuture<?> |
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 创建并执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作; 那就是执行将在initialDelay 之后开始,然后是initialDelay+period ,然后是initialDelay + 2 * period ,等等。 |
ScheduledFuture<?> |
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) 创建并执行在给定的初始延迟之后首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟。 |
7.5.2 ScheduledThreadPoolExcutor
ScheduledThreadPoolExcutor是ThreadPoolExecutor的子类,并实现了接口ThreadPoolExecutor。可以使得任务可以在给定延迟后运行,或定期执行。
提交的任务在运行之前被取消,执行被抑制。 默认情况下,这样一个取消的任务在工作队列中不会自动删除,直到其延迟过去。 虽然这样可以进一步检查和监控,但也可能导致取消任务的无限制保留。 为避免这种情况,请将setRemoveOnCancelPolicy(boolean)设置为true ,这将导致任务在取消时立即从工作队列中删除。
通过scheduleAtFixedRate或scheduleWithFixedDelay的任务的scheduleWithFixedDelay执行不重叠。 虽然不同的执行可以通过不同的线程来执行,先前执行的效果happen-before那些随后的那些的。
虽然这个类继承自ThreadPoolExecutor ,但是一些继承的调优方法对它没有用。 特别是因为它作为使用corePoolSize线程和无限队列的固定大小的池,对maximumPoolSize没有任何有用的效果。 此外,将corePoolSize设置为零或使用allowCoreThreadTimeOut几乎不是一个好主意,因为这可能会使池没有线程来处理任务,只要它们有资格运行。
扩展笔记:此类覆盖execute和submit方法以生成内部ScheduledFuture对象来控制每个任务的延迟和调度。 为了保护功能,子类中这些方法的任何进一步覆盖都必须调用超类版本,这有效地禁用其他任务自定义。 然而,此类提供替代保护扩展方法decorateTask (每一个用于一个版本Runnable和Callable ),其可以被用于定制用于执行经由输入的命令的具体任务类型execute , submit , schedule , scheduleAtFixedRate和scheduleWithFixedDelay 。 默认情况下, ScheduledThreadPoolExecutor使用扩展为FutureTask的任务类型。 但是,可以使用子类修改或替换。
Modifier and Type | Method and Description |
---|---|
protected <V> RunnableScheduledFuture<V> |
decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) 修改或替换用于执行可调用的任务。 |
protected <V> RunnableScheduledFuture<V> |
decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) 修改或替换用于执行runnable的任务。 |
void |
execute(Runnable command) 执行 command 零要求延迟。 |
boolean |
getContinueExistingPeriodicTasksAfterShutdownPolicy() 获得关于是否继续执行现有定期任务的策略,即使该执行者已经是 shutdown 。 |
boolean |
getExecuteExistingDelayedTasksAfterShutdownPolicy() 获得有关是否执行现有延迟任务的政策,即使这个执行者已经是 shutdown 。 |
BlockingQueue<Runnable> |
getQueue() 返回此执行程序使用的任务队列。 |
boolean |
getRemoveOnCancelPolicy() 获取关于在取消时是否应立即将已取消任务从工作队列中删除的策略。 |
<V> ScheduledFuture<V> |
schedule(Callable<V> callable, long delay, TimeUnit unit) 创建并执行在给定延迟后启用的ScheduledFuture。 |
ScheduledFuture<?> |
schedule(Runnable command, long delay, TimeUnit unit) 创建并执行在给定延迟后启用的单次操作。 |
ScheduledFuture<?> |
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 创建并执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作; 那就是执行将在initialDelay 之后开始,然后initialDelay+period ,然后是initialDelay + 2 * period 等等。 |
ScheduledFuture<?> |
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) 创建并执行在给定的初始延迟之后首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟。 |
void |
setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean value) 设置关于是否继续执行现有周期性任务的策略,即使该执行者已经是 shutdown 。 |
void |
setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean value) 设置关于是否执行现有延迟任务的策略,即使该执行者已经是 shutdown 。 |
void |
setRemoveOnCancelPolicy(boolean value) 设置取消时取消任务是否应立即从工作队列中删除的策略。 |
void |
shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。 |
List<Runnable> |
shutdownNow() 尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表。 |
<T> Future<T> |
submit(Callable<T> task) 提交值返回任务以执行,并返回代表任务待处理结果的Future。 |
Future<?> |
submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。 |
<T> Future<T> |
submit(Runnable task, T result) 提交一个可运行的任务执行,并返回一个表示该任务的未来。 |
7.5.3 简单应用
/* 自定义任务 */ class MyTask implements Runnable{ private int num; private String time; public MyTask(int num,String time){ this.num = num; this.time = time; } @Override public void run(){ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS"); System.out.println(String.format("%s: %s %s开始运行.......",Thread.currentThread().getName(),df.format(System.currentTimeMillis()),this)); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(String.format("%s: %s %s完成任务.",Thread.currentThread().getName(),df.format(System.currentTimeMillis()),this)); } @Override public String toString(){ return String.format("MyTask%s(预计运行时间%s)",this.num,this.time); } } public void test(){ ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(3); /* 创建任务运行 */ int tasknum=10; long delay = 0; Random random = new Random(); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS"); for(int i=0;i<tasknum;i++){ delay = 50+random.nextInt(1000); pool.schedule(new MyTask(i,df.format(System.currentTimeMillis()+delay)),delay, TimeUnit.MILLISECONDS); } pool.shutdown();//拒绝接受新任务 }
7.6 ExecutorCompletionService
CompletionService接口用于给创建异步任务和消费异步任务结果解耦,其实现类是ExecutorCompletionService 。
CompletionService并未继承Excutor接口,相关要点如下:
-
相比ExecutorService,CompletionService可以精确和简便地完成异步任务的执行。
-
CompletionService的一个实现是ExecutorCompletionService,它是Executor和BlockingQueue功能的融合体,Executor完成计算任务,BlockingQueue负责保存异步任务的执行结果。
-
在执行大量相互独立和同构的任务时,可以使用CompletionService
-
CompletionService可以为任务的执行设置时限,主要是通过BlockingQueue的poll(long time,TimeUnit unit)为任务执行结果的取得限制时间,如果没有完成就取消任务
7.6.1 CompletionService接口
接口CompletionService的功能是以异步的方式一边生产新的任务,一边处理已完成任务的结果,这样可以将执行任务与处理任务分离开来进行处理。使用submit执行任务,使用take取得已完成的任务,并按照完成这些任务的时间顺序处理它们的结果。
CompletionService 用于给创建异步任务和消费异步任务结果解耦。CompletionService 依赖 Executor 执行异步任务,而自己管理完成队列,维护异步任务执行结果。
CompletionService内部维护了一个阻塞队列BlockingQueue,如果没有任务完成,take()方法也会阻塞。
Modifier and Type | Method and Description |
---|---|
Future<V> |
poll() 检索并移除未来表示下一个已完成的任务,或 null ,如果不存在。 |
Future<V> |
poll(long timeout, TimeUnit unit) 检索并删除表示下一个完成的任务的未来,如果还没有,则等待必要时直到指定的等待时间。 |
Future<V> |
submit(Callable<V> task) 提交值返回任务以执行,并返回代表任务待处理结果的Future。 |
Future<V> |
submit(Runnable task, V result) 提交一个可运行的任务执行,并返回一个表示该任务的未来。 |
Future<V> |
take() 检索并删除代表下一个完成任务的未来,等待是否还没有任务。 |
7.6.2 ExecutorCompletionService
ExecutorCompletionService 是 CompletionService 实现类。ExecutorCompletionService 利用 AbstractExecutorService 构建 RunnableFuture(一个即表示任务又表示计算结果的对象),通过 Executor 提交任务,并在任务直接结束后将计算结果存到阻塞队列中。
构造方法如下:
Constructor and Description |
---|
ExecutorCompletionService(Executor executor) 使用提供的执行程序创建一个ExecutorCompletionService来执行基本任务,一个 |
ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue) 使用提供的执行程序创建一个ExecutorCompletionService,用于执行基本任务,并将提供的队列作为其完成队列。 |
成员方法如下:
Modifier and Type | Method and Description |
---|---|
Future<V> |
poll() 检索并删除代表下一个完成的任务的未来,或者如果没有任何的话将 null 。 |
Future<V> |
poll(long timeout, TimeUnit unit) 检索并删除表示下一个完成的任务的未来,如果还没有,则等待必要时直到指定的等待时间。 |
Future<V> |
submit(Callable<V> task) 提交值返回任务以执行,并返回代表任务待处理结果的Future。 |
Future<V> |
submit(Runnable task, V result) 提交一个可运行的任务执行,并返回一个表示该任务的未来。 |
Future<V> |
take() 检索并删除代表下一个完成任务的未来,等待是否还没有任务。 |
7.6.3 ExecutorCompletionService简单应用
可以看到CompletionService使用take()获得的返回值顺序与任务运行成功的顺序相同。
/* 有返回值的任务 */ class MyTask implements Callable<String>{ private String name; private int delay; public MyTask(int num,int delay){ this.name = "MyTask"+num; this.delay = delay; } @Override public String call(){ try { Thread.sleep(this.delay); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(String.format("%s:%s运行结束",Thread.currentThread().getName(),this.name)); return this.name+"finsh。"; } } public void test(){ ExecutorService pool = Executors.newCachedThreadPool(); CompletionService completionService = new ExecutorCompletionService(pool); /* 创建任务运行 */ int tasknum=10; Random random = new Random(); for(int i=0;i<tasknum;i++){ completionService.submit(new MyTask(i,10+random.nextInt(300))); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } pool.shutdown();//拒绝接受新任务 for(int i=0;i<tasknum;i++){ try { System.out.println(String.format("%s: 获得结果%s",Thread.currentThread().getName(),completionService.take().get())); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
7.7 工厂类Exectors
Exectors类作为工厂类,提供了一系列静态方法来配置线程池。Exectors封装了一系列方法,提供了多种线程池,简单的应用可以通过Exectors进行配置。
但是,同时Exectors配置的线程池是固定了几种,若是进行开发时,最好还是进行自定义线程池配置。
7.7.1 ThreadFactory
ThreadFactory 线程工厂,可以根据需要创建新线程的对象。使用ThreadFactory 接口可以删除new Thread的硬连线,使应用程序能够使用特殊的线程子类、优先级等。根据不同类型的线程,可以配置不同的线程工厂,类似于
class SimpleThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { return new Thread(r); } }
通过自定义ThreadFactory ,可以指定线程池中每个线程创建时进行统一的配置和动作。
Executors提供了两个方法返回两种ThreadFactory :
-
static ThreadFactory defaultThreadFactory() :返回用于创建新线程的默认线程工厂。DefaultThreadFactory是Executors的内部静态类,创建线程为非守护线程,优先级是默认优先级,统一命名,同时也进行了线程组配置。
-
static ThreadFactory privilegedThreadFactory() :返回一个用于创建与当前线程具有相同权限的新线程的线程工厂。PrivilegedThreadFactory继承了DefaultThreadFactory,同时进行了AccessController(java.security.AccessController)相关的配置。
AccessController类用于访问控制操作和决策更具体地说,AccessController类用于三个目的:
1) 根据当前有效的安全策略来决定是否允许或拒绝对关键系统资源的访问;
2)将代码标记为“特权”,从而影响随后的访问确定
3)为了获得当前呼叫上下文的“快照”,可以针对保存的上下文进行来自不同上下文的访问控制决定。
7.7.2 配置Callable对象
将一部分Callable对象的配置封装到工厂类Executes中,可以指定Callable在被调用时进行特定的配置和动作。
这部分涉及了很多AccessController相关的,后续再看看Java的安全模型。
-
传入Runnable
static Callable<Object> callable(Runnable task) //运行Runnable并返回null
static <T> Callable<T> callable(Runnable task, T result) //运行Runnable并返回result该方法返回一个Callable对象,当被调用时,它运行给定的任务并返回null 或者给定的结果result。 其代码中通常是使用定义在Executor内部的静态类Callable的子类RunnableAdapter进行封装。在RunnableAdapter的call方法中,通常是调用了task.run(),然后return null/return result。
-
PrivilegedAction和PrivilegedExceptionAction
static Callable<Object> callable(PrivilegedAction<?> action)
static Callable<Object> callable(PrivilegedExceptionAction<?> action)该方法返回一个Callable对象,当被调用时,它运行给定的特权动作(PrivilegedAction或者PrivilegedExceptionAction)并返回其结果。在其代码中通常会运行action.run()。
PrivilegedAction(java.security.PrivilegedAction)接口是要启用权限执行的计算,仅仅定义了一个run()方法。 通过调用PrivilegedAction对象上的PrivilegedAction来执行AccessController.doPrivileged 。 此接口仅用于不抛出检查异常的计算; 抛出检查异常的计算必须使用PrivilegedExceptionAction 。
PrivilegedExceptionAction(java.security.PrivilegedExceptionAction)接口是启用权限执行的计算,会抛出一个或多个已检查的异常。 通过调用
PrivilegedExceptionAction
对象上的PrivilegedExceptionAction
来执行AccessController.doPrivileged
。 此接口仅用于抛出已检查异常的计算; 不抛出检查异常的计算应该使用PrivilegedAction
。
-
PrivilegedCallable<T>
static <T> Callable<T> privilegedCallable(Callable<T> callable)
返回一个Callable对象,当被调用时,将在当前访问控制上下文中执行给定的callable 。 其返回的是PrivilegedCallable<T>(callable)。
PrivilegedCallable<T>也是Executor内部由
static final
所修饰的类,是Callable<T>的子类。static final class PrivilegedCallable<T> implements Callable<T> {
private final Callable<T> task;
private final AccessControlContext acc;
PrivilegedCallable(Callable<T> task) {
this.task = task;
this.acc = AccessController.getContext();
}
public T call() throws Exception {
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<T>() {
public T run() throws Exception {
return task.call();
}
}, acc);
} catch (PrivilegedActionException e) {
throw e.getException();
}
}
}
-
privilegedCallableUsingCurrentClassLoader
static <T> Callable<T> privilegedCallableUsingCurrentClassLoader(Callable<T> callable)
返回一个Callable对象,当被调用时,将在当前访问控制上下文中执行给定的callable ,当前上下文类加载器作为上下文类加载器。
7.7.3 配置ExecutorService
-
newCachedThreadPool
static ExecutorService newCachedThreadPool()
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
核心线程数为0,最大线程数无界,超时时间为60s,使用SynchronousQueue队列(不会缓存任务)。即,每来一个任务,如果有空闲线程,则使用,如没有则创建,当线程空闲60s后立即销毁。
适用于处理大量短时间偶发工作任务,即IO密集型任务。
-
newFixedThreadPool
static ExecutorService newFixedThreadPool(int nThreads)
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
核心线程数和最大线程数为同一传入参数,即,所有线程均为核心线程,永不销毁,循环复用,当无空闲线程时,任务进入无界的LinkedBlockingQueue中。
当任务量稳定,没有明显峰值和低谷,并且任务执行的时间不会太短的时候使用。适用于CPU密集型任务。
-
newSingleThreadExecutor
static ExecutorService newSingleThreadExecutor()
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
核心线程数为1,最大线程数也为1,使用LinkedBlockingQueue为缓存队列。即,该线程池只有一个永不销毁的工作线程,如该线程被占用,则进入无界队列中等待。
使用此线程池能保证所有的任务都是被顺序执行,最多会有一个任务处于活动状态。
-
newWorkStealingPool
static ExecutorService newWorkStealingPool()
static ExecutorService newWorkStealingPool(int parallelism)
Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。
-
unconfigurableExecutorService
static ExecutorService unconfigurableExecutorService(ExecutorService executor)
返回一个将所有定义的ExecutorService方法委托给给定执行程序的对象,但不能以其他方式使用转换方式访问。
7.7.4 配置ScheduledExecutorService
-
newScheduledThreadPool
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)核心线程数为传入参数(singleThreadScheduledExecutor将该参数指定为1),最大线程数无界,使用DelayedWorkQueue为阻塞队列。
该线程池适用于定期或者延迟执行的任务。
-
newSingleThreadScheduledExecutor
static ScheduledExecutorService newSingleThreadScheduledExecutor()
static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) -
unconfigurableScheduledExecutorService
static ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor)
返回一个将所有定义的ScheduledExecutorService方法委托给给定执行程序的对象,但不能以其他方式使用转换方式访问。
7.7.5 应用:Exectors配置线程池
Exectors作为工具类提供了一些方便的方法,可供直接返回固定配置的线程池,其中newCachedThreadPool无核心线程池,超时时间设置为60s。
public void test(){ //返回一个无核心线程,60S超时的线程池 //因为我要使用ThreadPoolExecutor的一些方法,就将ExeccutorService强制转换了 ThreadPoolExecutor pool = (ThreadPoolExecutor)Executors.newCachedThreadPool(); System.out.println("创建一个newCachedThreadPool。"); Runnable r = new Runnable() { @Override public void run() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("一个任务运行结束!"); } }; int taskNum = 10; for(int i=0;i<taskNum;i++){ pool.execute(r); System.out.println("将一个任务放入线程池,现在运行线程数:"+pool.getActiveCount()); } pool.shutdown(); try { Thread.sleep(15); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("等待60S......."); try { Thread.sleep(60000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("现在运行线程数:"+pool.getActiveCount()); }
7.x 参考
0、JAVA多线程编程
Java多线程编程所涉及的知识点包含线程创建、线程同步、线程间通信、线程死锁、线程控制(挂起、停止和恢复)。之前
-
篇0
-
篇1
-
篇2
-
篇3
-
篇4
-
篇5
-
篇6
-
篇7
-
篇8