第四章 执行器框架 学习知识要点
1.创建线程执行器
在开发大并发程序中,如果单纯的创建Runnable或Thread对象将非常麻烦,而且需要管理
这样的劣势:
1.必须实现所有与Thread对象管理相关的代码,比如线程的创建,结束以及结果的获取。
2.需要为每一个任务创建一个Thread对象,如果需要执行大量的任务,这将大大的影响应用程序的处理能力。
3.计算机的资源需要高效的进行控制和管理,如果创建过多的线程,将会导致系统负荷过重。
java5 后提供了一套机制解决这些问题,称为执行器框架(Executor FrameWork),围绕着Executor接口和其子接口ExecutorService,
以及实现这两个接口的ThreadPoolExecutor类展开。
这套机制分离了任务的创建和运行,通过使用执行器,仅需要实现Runnable接口的对象,然后将这些对象发送给执行器就行。
1.执行器会创建所需要的线程,来负责这些Runnable对象的创建,实例化以及运行。
2.执行器使用线程池来提高应用程序的性能,当发送一个任务给执行器时,执行器会尝试使用线程池中的线程来执行这个任务,
避免了不断创建和销毁线程而导致系统性能下降。
3.执行器框架带有一个可以返回结果的Callable接口,相对于Runnable的优势就是这个,提供了两点优势:
1.这个接口中的方法只有一个call(),可以返回结果。
2.当发送一个Callable对象给执行器时,将获得一个实现了Future接口的对象,可以使用这个对象来控制Callable对象的状态和结果。
线程执行器的主要方法:
1.getPoolSize():返回执行器线程池中实际线程的数量
2.getActiveCount():返回执行器中正在执行的任务线程的数量。
3.getCompletedTaskCount():返回执行器已经完成的任务数量。
4.execute(Runnable command):在未来摸个时间执行给定的命令,该命令可能在新的线程,已入池的线程或者正在调用的线程中执行,
者由Executor实现决定。
2.创建固定大小的线程执行器
Executor工厂类提供了一个方法来创建一个固定大小的线程执行器。这个执行器有一个线程数量的最大值,
如果发送超过这个最大值的任务
给执行器,执行器将不再创建额外的线程,剩下的任务将被阻塞到执行器有空闲的线程可用。
这个特性可以保证执行器不会给应用程序带来性能问题。
executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(5); //创建有五个线程的线程池
这里使用了一些ThreadPoolExecutor中的方法:
1.getPoolSize():返回执行器中线程的实际数量
2.getActiveCount():返回执行器正在执行任务的线程数量。
说明:Executors工厂类也提供newSingleThreadExecutor()方法。这是一个创建固定大小的线程执行器的一个极端场景。
将创建一个只有单个线程的执行器。因此
这个执行器在同一时间执行一个任务。
3.在执行器中执行任务并返回结果
执行器框架的两个接口实现该实例的效果,同时也是可以运行并发任务并返回结果的优势。
1.Callable:声明了一个call()方法。可以在这个方法里实现任务的具体逻辑操作。是个泛型接口,
必须声明call方法返回的数据类型。
2.Future:这个接口声明了一些方法获取由Callable对象产生的结果,并管理它们的状态。
Future接口中的主要方法:
1.cancel(boolean mayInterrupteIfRunning) :试图取消对此任务的执行。如果任务完成或者已经取消,或者由于其他原因而无法取消,
则此尝试将失败。当调用cancel方法时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,
则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。
2.isCancel():如果在任务正常完成前将其取消,则返回true.
3.isDone():如果任务已经完成,则返回true,可能由于正常终止,异常或者取消而完成,在所有这些情况中,此方法都将返回true。
4.get():如果有必要,将等待计算完成,然后获取其结果。
5.get(long timeout,TimeUnit unit):如果有必要,最多等待为使计算机完成所给定的时间后,获取其结果。
一般是获取call()方法返回的结果。
如果call()方法抛出异常,该方法也会抛出异常。
Executor的submit()方法会返回一个带泛型的Future对象。由Future对象管理线程返回的结果。
4.运行多个任务并处理第一个结果
并发编程中的常见问题就是,当采用多个并发任务解决一个问题时,往往只关心这些任务的第一个结果。这里我们使用ThreadPoolExecutor类实现该场景。
范例允许用户通过两种验证机制进行验证,但是只要有一种机制验证成功,那么这个用户就验证通过了。
关键在于ThreadPoolExecutor类的方法invokeAny()方法接收到一个任务列表,然后运行任务,返回第一个完成任务且没有抛出异常的任务执行结果。
这个方法返回的类型与任务里的call()
方法返回的类型相同,在这个范例中,将返回String类型的值。
这里有两个任务可以番红花true值,或者抛出Exception异常。因此有下面四种情况
1.如果两个任务都返回true值,那么InvokeAny()方法的结果就是首次完成任务的名称。
2.如果第一个任务返回true值,第二个任务抛出Exception异常,那么invokeAny方法的结果就是第一个任务的名称。
3.如果第一个任务抛出Exception异常,第二个任务返回true值,那么InvokeAny()方法的结果就是第二个任务的名称。
4.如果两个任务都抛出异常Exception,那么InvokeAny()方法将抛出ExecutionException异常。
该方法还有一个其他版本
invokeAny(Collection<? extends Callable<T>> tasks, long timeout,TimeUnit timeUnit),这个方法执行所有的任务,如果在给定的时间内某个任务已经完成,则返回其结果。
5.运行多个任务并处理所有结果
如果想要等待任务的结束,可以使用下面的方法:
1.如果任务执行结束,那么Future接口的isDone()方法将返回true。
2.在调用shutdown()方法后,ThreadPoolExecutor类的awaitTermination()方法会将线程休眠,直到所有的任务都执行完毕。
这两个方法有一些缺点
1.方法一中仅仅可以控制任务的完成与否。
2.方法二中必须关闭执行器来等待一个线程,否则调用这个方法的线程将立即返回。
ThreadPoolExecutor类还提供了一个方法,允许发送一个任务列表给执行器,并等待列表中所有任务执行完成。该方法就是InvokeAll(),返回一个List的Future对象。
关于InvokeAll()方法的重要地方,就是使用Future对象仅用来获取任务的结果。
当所有的任务执行结束时这个方法也执行结束了,如果在返回的Future对象上调用isDone()方法,那么所有的调用将返回true值。
另一个版本的方法:
InvokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit timeUnit)当所有的任务都执行完毕,或者超时的时候(无论哪个首先发生),
这个方法将返回
保持任务状态和结果的Future列表。
6.在执行器中延时执行任务
上面的情况都是创建执行器之后启动任务会马上执行,这里如果不想让任务马上被执行,
而是想让任务在过一段时间后才被执行,或者任务能够被周期性地执行。可以使用执行器框架提供的
ScheduledThreadPoolExecutor类
这里推荐利用Executors工厂类来创建,定时执行器。使用Executors工厂的newScheduledThreadPool()方法创建该实例对象。方法中的一个参数就是线程池中拥有的线程数量,。
在定时起中等待一段给定的时间后执行一个任务,需要使用schedule()方法,这个方法接收如下参数
1.即将执行的任务
2.任务执行前所要等待的时间。
3.等待时间的单位,由TimeUnit类的一个常量来指定。
备注:
1.如果想在一个给定的时间点来定时执行任务,那就需要计算这个给定时间点和当前时间的差异值,然后用这个差异值作为任务的延迟值。
2.也可以使用Runnable接口来实现任务,因为ScheduledThreadPoolExecutor类的Schedule()方法可以同时接受这两种类型的任务。
3.在调用shutdown方法而仍有待处理的任务需要执行时,可以配置ScheduledThreadPoolExecutor的行为。默认的行为是不论执行器是否结束,待处理的任务都将被执行。
但是通过调用该类的setExecuteExistingDelayedTasksAfterShutdownPolicy方法则可以改变这个行为。传递false参数给这个方法,执行shutdown()方法后待处理的任务将不再执行。
7.在执行器中周期性执行任务
有时候任务需要周期性的执行,就需要使用ScheduledThreadPoolExecutor类来执行周期性的任务。
先需要创建一个ScheduledExecutorService对象。同创建执行器一样,在java中推荐使用Executors工厂类来创建ScheduledExecutorService对象。
Executors类就是执行器对象的工厂
。在本范例中使用scheduledAtFixedRate()方法发送任务。这个方法接收4个参数,分别为将被周期性执行的任务,任务第一次执行后的延时时间,
两次执行的时间周期,以及第二第三个参数的单位。
需要注意的是,两次执行之间的周期是指任务在两次执行开始时的时间间隔。
scheduleAtFixedRate()方法返回一个ScheduledFuture对象,ScheduleFuture接口扩展了Future接口,带有定时任务相关的操作方法。ScheduledFuture是一个
泛型参数化的接口。在该范例中,任务是Runnable对象,并没有泛型参数化,必须通过?符号作为参数来泛型化。
可以使用ScheduledFuture接口中的一个方法,getDelay()方法返回任务到下一次执行时锁要等待的剩余时间。这个方法接收一个TimeUnit常量作为时间单位。
8.在执行器中取消任务
使用执行器时,不需要管理线程,只需要实现Runnable或者Callable任务并发送任务给执行器即可。执行器负责创建线程,
管理线程池中的线程,当线程不再需要时就销毁它们。
可以使用Future接口中的cancel方法将任务取消。根据调用该方法时锁传递的参数以及任务的状态,这个方法的行为有些不同。
1.如果任务已经完成,或者之前已经被取消,或者由于某些原因不能被取消,那么方法将返回false并且任务也不能取消。
2.如果任务爱执行器中等待分配Thread对象来执行它,那么任务被取消,并且不会开始执行。如果任务已经在运行,
那么它依赖于调用cancel方法时所传递的参数,
如果为true则运行的该任务被取消。如果传递的参数为false,该运行的任务不会被取消。
备注:如果Future对象所控制的任务已经被取消,那么使用Future对象的get()方法时将抛出CancellationException异常。
9.在执行器中控制任务的完成
FutureTask类提供了一个名为done()的方法,允许在执行器中的任务执行结束后,还可以执行一些代码。
这个方法可以被用来执行一些后期处理操作。
比如产生报表,通过邮件发送结果或者释放一些系统资源。当任务执行完成是受FutureTask类控制时,
这个方法在内部被FutureTask类调用。
在任务结果设置后以及任务的状态以改变为isDone()之后,无论任务是否被取消,或者正常结束,done()方法才被调用。
默认情况下,done()方法的实现为空,即没有任何具体的代码实现,我们可以覆盖FutureTask类并实现done()方法来改变这种行为。
说明:一般就是继承FutureTask类重写该done()方法
在创建好返回值以及改变任务状态为isDone之后,FutureTask类就会在内部调用done()方法,虽然我们无法改变任务的结果值,
也无法改变任务的状态,但是可以通过任务来关闭系统
资源,输出日志信息,发送通知等。
10.在执行器中分离任务的启动与结果的处理
当我们要在一个对象中发送任务给执行器,在另一个对象中处理结果时,对于这种需求可以使用CompletionService类。
该类有一个方法用来发送任务给执行器,还有一个方法为下一个已经执行结束的任务获取Future对象。从内部实现机制来看,CompletionService类使用Executor对象来执行任务。
这个行为
的优势是可以共享CompletionService对象,并发送任务到执行器,然后其他的对象可以处理任务的结果。第二个方法有不足之处,只能为已经执行结束的任务获取Future对象,因此,
这些Future对象只能被用来获取任务的结果。
该类可以执行Callable或者Runnable类型的任务。由于使用Runnable接口对象不能产生结果,因此CompletionService的基本原则不适用。
CompletionService类提供了其他两种方法来获取任务已经完成的Future对象。
1.poll():无参数的poll()方法用于检查队列中是否有Future对象。如果队列为空,则立即返回null,否则将返回队列中的第一个元素,并移除该元素。
2.take(): 这个方法检查队列中是否有Future对象。如果队列为空,则会阻塞线程直到队列中有可用的元素。如果队列中有元素,它将返回队列中的第一个元素,并移除该元素。
11.处理在执行器中被拒绝的任务
如果在shutdown()方法与执行器结束之间发送一个任务给执行器,这个任务会被拒绝,因为这个时间段执行器不再接收任务了,
ThreadPoolExecutor类提供了一套机制,当任务被拒绝时调用
这套机制来处理它们。
使用RejectedExecutionHandler接口。
为了处理在执行器中被拒绝的任务,需要创建一个实现RejectedExecutionHandler接口的处理类。这个接口有一个rejectedExecution方法,其中有两个参数:
1.一个Runnable对象,用来存储被拒绝的任务,。
2.一个Executor对象,用来存储任务被拒绝的执行器。
被执行器拒绝的每一个任务都将调用该方法,需要先调用Executor类的setRejectedExecutionHandler()方法来设置用于被拒绝的任务的处理程序。
当执行器接收一个任务并开始执行时,先检查shutdown()方法是否已经被调用了,如果是那么执行器就会拒绝这个任务。首先,执行器会寻找通过setRejectedExecutionHandler()方法设置的
用于被拒绝的任务的处理程序,如果找到一个处理程序,执行器就会调用其rejectedExecution()方法,否则就会抛出RejectedExecutionException异常,这是一个运行时异常,
因此并不需要catch语句来对其进行处理。
1.执行器框架中的类,接口和常用方法
1.接口
Executors:
方法:
newFixedThreadPool: 创建固定大小的线程执行器,接收一个参数表示创建线程数量,如果发送超过线程数的任务给执行器,剩余的任务将被阻塞直到线程池里有空闲的线程来处理它们
内部会维护一个无界队列,当做任务的存储数据结构。--->返回: ExecutorService
newSingleThreadExecutor:创建只有一个线程的线程执行器,
newCachedThreadPool: 创建具有缓存功能的线程池,仅当线程的数量是合理的或者线程只会运行很短的时间,或者是任务运行时间很短的异步任务,适合采用该方法创建执行器。
newSingleThreadScheduledExecutor:创建周期性单线程执行器,适用于单线程执行周期性任务 ---->返回:ScheduledExecutorService
newScheduledThreadPool:创建可以延迟执行或者周期性执行的线程执行器
实现类:
描述:创建线程执行器或者线程池的工厂接口。
ExecutorService:
方法:
shutdown:终止线程池或者线程执行器,但是允许执行已经提交的任务,而未提交的任务则不执行。
shutdownNow: 终止线程池或者线程执行器,而且阻止未提交的任务提交,并且试图停止当前已经提交运行的任务。
在终止时没有任务在执行也没有任务在等待执行,并且无法提交新任务。 返回---->等待执行任务的列表
isShutdown: 如果调用了上面两种方法之一,则该方法返回true
isTerminated:如果调用了上面两种方法之一,并且执行器完成了关闭的过程,那么该方法返回true
awaitTermination: 在发送终止线程执行器请求后这个方法将阻塞所调用的线程直到执行器完成任务或者达到所指定的timeout值或者当前执行线程被中断。
描述:线程执行器的主要接口,
submit(): 发送任务给执行器,该方法可以发送Callable对象的任务和Runnable对象的任务给线程执行器,由线程执行器执行,无论是哪个对象都将返回
Future对象或者是Future列表,作为任务的返回值。
invokeAll():发送一个任务列表给执行器,并且返回这个任务列表中的所有执行结果。
invokeAny():发送一个任务列表给执行器,并且返回这个任务列表中第一个任务执行结束的结果。
与submit方法不同的是上面这两种方法可以一次发送很多任务,而submit只能一次发送一个任务给执行器.
RejectedExecutionHandler:
方法:
rejectedExecution: 处理执行器中被拒绝的任务,可以获取执行器的状态,同时通过ThreadPoolExecutor中的setRejectedExecutionHandler()方法设置该对象,并处理被拒绝的任务。
ThreadFactory:
方法:newThread(Runnable r):创建线程
描述:创建线程的工厂,可以实现制定化
Callable:
方法 call:线程执行方法,返回泛型声明对象
Future:
方法:
1.cancel(boolean mayInterrupteIfRunning) :试图取消对此任务的执行。如果任务完成或者已经取消,或者由于其他原因而无法取消,
则此尝试将失败。当调用cancel方法时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,
则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。
2.isCancel():如果在任务正常完成前将其取消,则返回true.
3.isDone():如果任务已经完成,则返回true,可能由于正常终止,异常或者取消而完成,在所有这些情况中,此方法都将返回true。
4.get():如果有必要,将等待计算完成,然后获取其结果。
5.get(long timeout,TimeUnit unit):如果有必要,最多等待为使计算机完成所给定的时间后,获取其结果。
一般是获取call()方法返回的结果。
如果call()方法抛出异常,该方法也会抛出异常。
Executor的submit()方法会返回一个带泛型的Future对象。由Future对象管理线程返回的结果。
ScheduledFuture:
该接口返回具有延迟或者周期性执行任务的执行器的结果,是Future接口的子接口
该接口中没有具体方法
RunnableFuture:
CompletionService:
submit(Runnable r):提交一个任务,并且返回一个Future对象,如果这个任务没有被安排执行,或者任务为空都会抛出异常
take():这个方法没有参数,检查队列中是否有Future对象,如果队列为空,将阻塞线程直到队列中有可用的元素。如果队列中
有元素,将返回队列中的第一个元素,并移除该元素。
poll():无参数的poll()方法用于检查队列中是否有Future对象,如果队列为空,则立即返回null,否则,将返回队列中的第一个元素,并移除这个元素。
ScheduledExecutorService:
方法:
schedule(Runnable command,long delay, TimeUnit unit):该方法将任务发送到线程池中执行,与submit(),execute()功能类似,同时该方法只执行一次
同时该方法的另一个重载方法可以接受Callable对象的任务
scheduleAtFixedRate:以固定的速率执行任务,就是周期性的执行某一任务,
scheduleWithFixedDelay: 以固定的延迟执行任务,周期性的延迟多少时间执行某一任务
描述:用来分离任务的启动与结果的处理。
2.类
ThreadPoolExecutor:
方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler):
该方法是该类的构造函数,其他构造函数均调用该方法实例化,不过创建线程执行器一般通过Executors工厂接口创建避免添加很多参数,使用方便,推荐上面的使用方式。
execute(Runnable command):该方法是Executor接口中唯一的方法,是线程执行器中具体执行任务的方法
getPoolSize():返回执行器中线程的实际数量。
getActiveCount():返回执行器正在执行任务的线程数量,这只是返回的估计值,不是准确值。
getLargestPoolSize:返回线程池中能同时存在的最多线程数量。
getTaskCount(): 返回等待执行的任务数量,该值只是返回的估计值,不是准确值,因为线程池中的任务数量是动态改变的。
getCompletedTaskCount():返回线程池中已经执行完毕的任务数量,该值同样是估计值,因为线程池中的任务数量是动态改变的。
purge():移除被取消的任务,该方法可能执行失败,因为被取消的任务可能被某些工作者线程干扰而取消失败。
线程执行器框架的主要实现类,该类实现了ExecuteService接口。可以创建不同形式的线程执行器,并提供多种方法执行任务,管理返回结果。
1.创建线程执行器
在开发大并发程序中,如果单纯的创建Runnable或Thread对象将非常麻烦,而且需要管理
这样的劣势:
1.必须实现所有与Thread对象管理相关的代码,比如线程的创建,结束以及结果的获取。
2.需要为每一个任务创建一个Thread对象,如果需要执行大量的任务,这将大大的影响应用程序的处理能力。
3.计算机的资源需要高效的进行控制和管理,如果创建过多的线程,将会导致系统负荷过重。
java5 后提供了一套机制解决这些问题,称为执行器框架(Executor FrameWork),围绕着Executor接口和其子接口ExecutorService,
以及实现这两个接口的ThreadPoolExecutor类展开。
这套机制分离了任务的创建和运行,通过使用执行器,仅需要实现Runnable接口的对象,然后将这些对象发送给执行器就行。
1.执行器会创建所需要的线程,来负责这些Runnable对象的创建,实例化以及运行。
2.执行器使用线程池来提高应用程序的性能,当发送一个任务给执行器时,执行器会尝试使用线程池中的线程来执行这个任务,
避免了不断创建和销毁线程而导致系统性能下降。
3.执行器框架带有一个可以返回结果的Callable接口,相对于Runnable的优势就是这个,提供了两点优势:
1.这个接口中的方法只有一个call(),可以返回结果。
2.当发送一个Callable对象给执行器时,将获得一个实现了Future接口的对象,可以使用这个对象来控制Callable对象的状态和结果。
线程执行器的主要方法:
1.getPoolSize():返回执行器线程池中实际线程的数量
2.getActiveCount():返回执行器中正在执行的任务线程的数量。
3.getCompletedTaskCount():返回执行器已经完成的任务数量。
4.execute(Runnable command):在未来摸个时间执行给定的命令,该命令可能在新的线程,已入池的线程或者正在调用的线程中执行,
者由Executor实现决定。
2.创建固定大小的线程执行器
Executor工厂类提供了一个方法来创建一个固定大小的线程执行器。这个执行器有一个线程数量的最大值,
如果发送超过这个最大值的任务
给执行器,执行器将不再创建额外的线程,剩下的任务将被阻塞到执行器有空闲的线程可用。
这个特性可以保证执行器不会给应用程序带来性能问题。
executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(5); //创建有五个线程的线程池
这里使用了一些ThreadPoolExecutor中的方法:
1.getPoolSize():返回执行器中线程的实际数量
2.getActiveCount():返回执行器正在执行任务的线程数量。
说明:Executors工厂类也提供newSingleThreadExecutor()方法。这是一个创建固定大小的线程执行器的一个极端场景。
将创建一个只有单个线程的执行器。因此
这个执行器在同一时间执行一个任务。
3.在执行器中执行任务并返回结果
执行器框架的两个接口实现该实例的效果,同时也是可以运行并发任务并返回结果的优势。
1.Callable:声明了一个call()方法。可以在这个方法里实现任务的具体逻辑操作。是个泛型接口,
必须声明call方法返回的数据类型。
2.Future:这个接口声明了一些方法获取由Callable对象产生的结果,并管理它们的状态。
Future接口中的主要方法:
1.cancel(boolean mayInterrupteIfRunning) :试图取消对此任务的执行。如果任务完成或者已经取消,或者由于其他原因而无法取消,
则此尝试将失败。当调用cancel方法时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,
则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。
2.isCancel():如果在任务正常完成前将其取消,则返回true.
3.isDone():如果任务已经完成,则返回true,可能由于正常终止,异常或者取消而完成,在所有这些情况中,此方法都将返回true。
4.get():如果有必要,将等待计算完成,然后获取其结果。
5.get(long timeout,TimeUnit unit):如果有必要,最多等待为使计算机完成所给定的时间后,获取其结果。
一般是获取call()方法返回的结果。
如果call()方法抛出异常,该方法也会抛出异常。
Executor的submit()方法会返回一个带泛型的Future对象。由Future对象管理线程返回的结果。
4.运行多个任务并处理第一个结果
并发编程中的常见问题就是,当采用多个并发任务解决一个问题时,往往只关心这些任务的第一个结果。这里我们使用ThreadPoolExecutor类实现该场景。
范例允许用户通过两种验证机制进行验证,但是只要有一种机制验证成功,那么这个用户就验证通过了。
关键在于ThreadPoolExecutor类的方法invokeAny()方法接收到一个任务列表,然后运行任务,返回第一个完成任务且没有抛出异常的任务执行结果。
这个方法返回的类型与任务里的call()
方法返回的类型相同,在这个范例中,将返回String类型的值。
这里有两个任务可以番红花true值,或者抛出Exception异常。因此有下面四种情况
1.如果两个任务都返回true值,那么InvokeAny()方法的结果就是首次完成任务的名称。
2.如果第一个任务返回true值,第二个任务抛出Exception异常,那么invokeAny方法的结果就是第一个任务的名称。
3.如果第一个任务抛出Exception异常,第二个任务返回true值,那么InvokeAny()方法的结果就是第二个任务的名称。
4.如果两个任务都抛出异常Exception,那么InvokeAny()方法将抛出ExecutionException异常。
该方法还有一个其他版本
invokeAny(Collection<? extends Callable<T>> tasks, long timeout,TimeUnit timeUnit),这个方法执行所有的任务,如果在给定的时间内某个任务已经完成,则返回其结果。
5.运行多个任务并处理所有结果
如果想要等待任务的结束,可以使用下面的方法:
1.如果任务执行结束,那么Future接口的isDone()方法将返回true。
2.在调用shutdown()方法后,ThreadPoolExecutor类的awaitTermination()方法会将线程休眠,直到所有的任务都执行完毕。
这两个方法有一些缺点
1.方法一中仅仅可以控制任务的完成与否。
2.方法二中必须关闭执行器来等待一个线程,否则调用这个方法的线程将立即返回。
ThreadPoolExecutor类还提供了一个方法,允许发送一个任务列表给执行器,并等待列表中所有任务执行完成。该方法就是InvokeAll(),返回一个List的Future对象。
关于InvokeAll()方法的重要地方,就是使用Future对象仅用来获取任务的结果。
当所有的任务执行结束时这个方法也执行结束了,如果在返回的Future对象上调用isDone()方法,那么所有的调用将返回true值。
另一个版本的方法:
InvokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit timeUnit)当所有的任务都执行完毕,或者超时的时候(无论哪个首先发生),
这个方法将返回
保持任务状态和结果的Future列表。
6.在执行器中延时执行任务
上面的情况都是创建执行器之后启动任务会马上执行,这里如果不想让任务马上被执行,
而是想让任务在过一段时间后才被执行,或者任务能够被周期性地执行。可以使用执行器框架提供的
ScheduledThreadPoolExecutor类
这里推荐利用Executors工厂类来创建,定时执行器。使用Executors工厂的newScheduledThreadPool()方法创建该实例对象。方法中的一个参数就是线程池中拥有的线程数量,。
在定时起中等待一段给定的时间后执行一个任务,需要使用schedule()方法,这个方法接收如下参数
1.即将执行的任务
2.任务执行前所要等待的时间。
3.等待时间的单位,由TimeUnit类的一个常量来指定。
备注:
1.如果想在一个给定的时间点来定时执行任务,那就需要计算这个给定时间点和当前时间的差异值,然后用这个差异值作为任务的延迟值。
2.也可以使用Runnable接口来实现任务,因为ScheduledThreadPoolExecutor类的Schedule()方法可以同时接受这两种类型的任务。
3.在调用shutdown方法而仍有待处理的任务需要执行时,可以配置ScheduledThreadPoolExecutor的行为。默认的行为是不论执行器是否结束,待处理的任务都将被执行。
但是通过调用该类的setExecuteExistingDelayedTasksAfterShutdownPolicy方法则可以改变这个行为。传递false参数给这个方法,执行shutdown()方法后待处理的任务将不再执行。
7.在执行器中周期性执行任务
有时候任务需要周期性的执行,就需要使用ScheduledThreadPoolExecutor类来执行周期性的任务。
先需要创建一个ScheduledExecutorService对象。同创建执行器一样,在java中推荐使用Executors工厂类来创建ScheduledExecutorService对象。
Executors类就是执行器对象的工厂
。在本范例中使用scheduledAtFixedRate()方法发送任务。这个方法接收4个参数,分别为将被周期性执行的任务,任务第一次执行后的延时时间,
两次执行的时间周期,以及第二第三个参数的单位。
需要注意的是,两次执行之间的周期是指任务在两次执行开始时的时间间隔。
scheduleAtFixedRate()方法返回一个ScheduledFuture对象,ScheduleFuture接口扩展了Future接口,带有定时任务相关的操作方法。ScheduledFuture是一个
泛型参数化的接口。在该范例中,任务是Runnable对象,并没有泛型参数化,必须通过?符号作为参数来泛型化。
可以使用ScheduledFuture接口中的一个方法,getDelay()方法返回任务到下一次执行时锁要等待的剩余时间。这个方法接收一个TimeUnit常量作为时间单位。
8.在执行器中取消任务
使用执行器时,不需要管理线程,只需要实现Runnable或者Callable任务并发送任务给执行器即可。执行器负责创建线程,
管理线程池中的线程,当线程不再需要时就销毁它们。
可以使用Future接口中的cancel方法将任务取消。根据调用该方法时锁传递的参数以及任务的状态,这个方法的行为有些不同。
1.如果任务已经完成,或者之前已经被取消,或者由于某些原因不能被取消,那么方法将返回false并且任务也不能取消。
2.如果任务爱执行器中等待分配Thread对象来执行它,那么任务被取消,并且不会开始执行。如果任务已经在运行,
那么它依赖于调用cancel方法时所传递的参数,
如果为true则运行的该任务被取消。如果传递的参数为false,该运行的任务不会被取消。
备注:如果Future对象所控制的任务已经被取消,那么使用Future对象的get()方法时将抛出CancellationException异常。
9.在执行器中控制任务的完成
FutureTask类提供了一个名为done()的方法,允许在执行器中的任务执行结束后,还可以执行一些代码。
这个方法可以被用来执行一些后期处理操作。
比如产生报表,通过邮件发送结果或者释放一些系统资源。当任务执行完成是受FutureTask类控制时,
这个方法在内部被FutureTask类调用。
在任务结果设置后以及任务的状态以改变为isDone()之后,无论任务是否被取消,或者正常结束,done()方法才被调用。
默认情况下,done()方法的实现为空,即没有任何具体的代码实现,我们可以覆盖FutureTask类并实现done()方法来改变这种行为。
说明:一般就是继承FutureTask类重写该done()方法
在创建好返回值以及改变任务状态为isDone之后,FutureTask类就会在内部调用done()方法,虽然我们无法改变任务的结果值,
也无法改变任务的状态,但是可以通过任务来关闭系统
资源,输出日志信息,发送通知等。
10.在执行器中分离任务的启动与结果的处理
当我们要在一个对象中发送任务给执行器,在另一个对象中处理结果时,对于这种需求可以使用CompletionService类。
该类有一个方法用来发送任务给执行器,还有一个方法为下一个已经执行结束的任务获取Future对象。从内部实现机制来看,CompletionService类使用Executor对象来执行任务。
这个行为
的优势是可以共享CompletionService对象,并发送任务到执行器,然后其他的对象可以处理任务的结果。第二个方法有不足之处,只能为已经执行结束的任务获取Future对象,因此,
这些Future对象只能被用来获取任务的结果。
该类可以执行Callable或者Runnable类型的任务。由于使用Runnable接口对象不能产生结果,因此CompletionService的基本原则不适用。
CompletionService类提供了其他两种方法来获取任务已经完成的Future对象。
1.poll():无参数的poll()方法用于检查队列中是否有Future对象。如果队列为空,则立即返回null,否则将返回队列中的第一个元素,并移除该元素。
2.take(): 这个方法检查队列中是否有Future对象。如果队列为空,则会阻塞线程直到队列中有可用的元素。如果队列中有元素,它将返回队列中的第一个元素,并移除该元素。
11.处理在执行器中被拒绝的任务
如果在shutdown()方法与执行器结束之间发送一个任务给执行器,这个任务会被拒绝,因为这个时间段执行器不再接收任务了,
ThreadPoolExecutor类提供了一套机制,当任务被拒绝时调用
这套机制来处理它们。
使用RejectedExecutionHandler接口。
为了处理在执行器中被拒绝的任务,需要创建一个实现RejectedExecutionHandler接口的处理类。这个接口有一个rejectedExecution方法,其中有两个参数:
1.一个Runnable对象,用来存储被拒绝的任务,。
2.一个Executor对象,用来存储任务被拒绝的执行器。
被执行器拒绝的每一个任务都将调用该方法,需要先调用Executor类的setRejectedExecutionHandler()方法来设置用于被拒绝的任务的处理程序。
当执行器接收一个任务并开始执行时,先检查shutdown()方法是否已经被调用了,如果是那么执行器就会拒绝这个任务。首先,执行器会寻找通过setRejectedExecutionHandler()方法设置的
用于被拒绝的任务的处理程序,如果找到一个处理程序,执行器就会调用其rejectedExecution()方法,否则就会抛出RejectedExecutionException异常,这是一个运行时异常,
因此并不需要catch语句来对其进行处理。
1.执行器框架中的类,接口和常用方法
1.接口
Executors:
方法:
newFixedThreadPool: 创建固定大小的线程执行器,接收一个参数表示创建线程数量,如果发送超过线程数的任务给执行器,剩余的任务将被阻塞直到线程池里有空闲的线程来处理它们
内部会维护一个无界队列,当做任务的存储数据结构。--->返回: ExecutorService
newSingleThreadExecutor:创建只有一个线程的线程执行器,
newCachedThreadPool: 创建具有缓存功能的线程池,仅当线程的数量是合理的或者线程只会运行很短的时间,或者是任务运行时间很短的异步任务,适合采用该方法创建执行器。
newSingleThreadScheduledExecutor:创建周期性单线程执行器,适用于单线程执行周期性任务 ---->返回:ScheduledExecutorService
newScheduledThreadPool:创建可以延迟执行或者周期性执行的线程执行器
实现类:
描述:创建线程执行器或者线程池的工厂接口。
ExecutorService:
方法:
shutdown:终止线程池或者线程执行器,但是允许执行已经提交的任务,而未提交的任务则不执行。
shutdownNow: 终止线程池或者线程执行器,而且阻止未提交的任务提交,并且试图停止当前已经提交运行的任务。
在终止时没有任务在执行也没有任务在等待执行,并且无法提交新任务。 返回---->等待执行任务的列表
isShutdown: 如果调用了上面两种方法之一,则该方法返回true
isTerminated:如果调用了上面两种方法之一,并且执行器完成了关闭的过程,那么该方法返回true
awaitTermination: 在发送终止线程执行器请求后这个方法将阻塞所调用的线程直到执行器完成任务或者达到所指定的timeout值或者当前执行线程被中断。
描述:线程执行器的主要接口,
submit(): 发送任务给执行器,该方法可以发送Callable对象的任务和Runnable对象的任务给线程执行器,由线程执行器执行,无论是哪个对象都将返回
Future对象或者是Future列表,作为任务的返回值。
invokeAll():发送一个任务列表给执行器,并且返回这个任务列表中的所有执行结果。
invokeAny():发送一个任务列表给执行器,并且返回这个任务列表中第一个任务执行结束的结果。
与submit方法不同的是上面这两种方法可以一次发送很多任务,而submit只能一次发送一个任务给执行器.
RejectedExecutionHandler:
方法:
rejectedExecution: 处理执行器中被拒绝的任务,可以获取执行器的状态,同时通过ThreadPoolExecutor中的setRejectedExecutionHandler()方法设置该对象,并处理被拒绝的任务。
ThreadFactory:
方法:newThread(Runnable r):创建线程
描述:创建线程的工厂,可以实现制定化
Callable:
方法 call:线程执行方法,返回泛型声明对象
Future:
方法:
1.cancel(boolean mayInterrupteIfRunning) :试图取消对此任务的执行。如果任务完成或者已经取消,或者由于其他原因而无法取消,
则此尝试将失败。当调用cancel方法时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,
则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。
2.isCancel():如果在任务正常完成前将其取消,则返回true.
3.isDone():如果任务已经完成,则返回true,可能由于正常终止,异常或者取消而完成,在所有这些情况中,此方法都将返回true。
4.get():如果有必要,将等待计算完成,然后获取其结果。
5.get(long timeout,TimeUnit unit):如果有必要,最多等待为使计算机完成所给定的时间后,获取其结果。
一般是获取call()方法返回的结果。
如果call()方法抛出异常,该方法也会抛出异常。
Executor的submit()方法会返回一个带泛型的Future对象。由Future对象管理线程返回的结果。
ScheduledFuture:
该接口返回具有延迟或者周期性执行任务的执行器的结果,是Future接口的子接口
该接口中没有具体方法
RunnableFuture:
CompletionService:
submit(Runnable r):提交一个任务,并且返回一个Future对象,如果这个任务没有被安排执行,或者任务为空都会抛出异常
take():这个方法没有参数,检查队列中是否有Future对象,如果队列为空,将阻塞线程直到队列中有可用的元素。如果队列中
有元素,将返回队列中的第一个元素,并移除该元素。
poll():无参数的poll()方法用于检查队列中是否有Future对象,如果队列为空,则立即返回null,否则,将返回队列中的第一个元素,并移除这个元素。
ScheduledExecutorService:
方法:
schedule(Runnable command,long delay, TimeUnit unit):该方法将任务发送到线程池中执行,与submit(),execute()功能类似,同时该方法只执行一次
同时该方法的另一个重载方法可以接受Callable对象的任务
scheduleAtFixedRate:以固定的速率执行任务,就是周期性的执行某一任务,
scheduleWithFixedDelay: 以固定的延迟执行任务,周期性的延迟多少时间执行某一任务
描述:用来分离任务的启动与结果的处理。
2.类
ThreadPoolExecutor:
方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler):
该方法是该类的构造函数,其他构造函数均调用该方法实例化,不过创建线程执行器一般通过Executors工厂接口创建避免添加很多参数,使用方便,推荐上面的使用方式。
execute(Runnable command):该方法是Executor接口中唯一的方法,是线程执行器中具体执行任务的方法
getPoolSize():返回执行器中线程的实际数量。
getActiveCount():返回执行器正在执行任务的线程数量,这只是返回的估计值,不是准确值。
getLargestPoolSize:返回线程池中能同时存在的最多线程数量。
getTaskCount(): 返回等待执行的任务数量,该值只是返回的估计值,不是准确值,因为线程池中的任务数量是动态改变的。
getCompletedTaskCount():返回线程池中已经执行完毕的任务数量,该值同样是估计值,因为线程池中的任务数量是动态改变的。
purge():移除被取消的任务,该方法可能执行失败,因为被取消的任务可能被某些工作者线程干扰而取消失败。
线程执行器框架的主要实现类,该类实现了ExecuteService接口。可以创建不同形式的线程执行器,并提供多种方法执行任务,管理返回结果。