线程执行器
简介
通常,使用Java来开发一个简单的并发应用程序,会创建一些Runnable对象,然后创建对应的Thread对象来执行他们。但是,如果需要开发一个程序来运行大量的并发任务,这个方法将突显如下的劣势:
必须实现所有与Thread对象管理相关的代码,比如线程的创建、结束以及结果的获取。
需要为每一个对象创建一个Thread对象。如果需要执行大量的任务,这将大大的影响应用程序的处理能力。
计算机的资源需要高效的进行控制和管理,如果创建过多的线程,将会导致系统负荷过重。
Java API提供了一套意在解决这些问题的机制,这套机制称之为执行器框架,围绕着Execute接口和它的子接口ExecutorService,以及实现这两个接口的ThreadPoolExecutor类。这套机制分离了任务的创建和执行,通过使用执行器,仅需要实现Runnable接口的对象,然后将这些对象发送给执行器即可。执行器通过创建所需的线程来负责这些Runnable对象的创建、实例化以及运行。但是执行器功能不限于此。它使用了线程池来提高应用程序的性能,避免了不断的创建和销毁线程而导致系统性能下降。
执行器框架另一个重要的优势是Callable接口,它类似于Runable接口,但是提供了两方面的增强。
这个接口的主方法称为call()可返回结果集。
当发送一个callable对象给执行器,将获得一个实现Future接口的对象,可以使用这个对象来控制callable对象的状态和结果。
创建线程执行器
使用执行器框架的第一步是创建ThreadPoolExecutor对象。可以ThreadPoolExecutor类提供的四个构造器或者使用Executors工厂类来创建ThreadPoolExecutor对象。一旦有了执行器,就可以将Runnable或callable对象发送给它去执行了。
仅当线程的数量是合理的或者线程只会运行很短耳朵时间时,适合采用Executors工厂类的newCachedThreadPool()方法来创建执行器。
executor=(ThreadPoolExecutor)Executors.newCachedThreadPool();
创建固定大小的线程执行器
当使用Executors类的newCachedThreadPool()方法创建基本的ThreadPoolExecutor时,执行器运行过程中将碰到线程数量的问题。如果线程池中没有空闲的线程可用,那么执行器将为接收到的每一个任务创建一个新线程,当发送大量的任务给执行器并且任务需要持续较长的时间时,系统将会超负荷,应用程序也将随之性能不佳。
executor=(ThreadPoolExecutor)Executors.newFixedThreadPool(5);
使用Executors类的newFixedThreadPool()方法来创建执行器,这个方法创建了具有线程最大数量值得执行器,如果发送超过线程数的任务给执行器,剩余的任务将被阻塞直到线程池里有空余的线程来处理他们。
Executors工厂类也提供了newSingleThreadExecutor()方法,这是一个创建固定大小线程执行器的极端场景,它将创建一个只有单个线程的执行器,因此,这个执行器只能在同一时间执行一个任务。
在执行器中执行任务并返回结果
执行器框架的优势之一是可以运行并发任务并返回结果。
Callable:这个接口声明了call()方法。可以在这个方法里实现任务的具体逻辑操作。Callable接口是一个泛型接口,这就意味着必须声明call()方法返回的数据类型。
Future:这个接口声明了一起方法获取由Callable对象产生的结果,并管理他们的状态。
运行多个任务并处理第一个结果
当采用多个并发任务来解决一个问题时,往往只关心这些任务中的第一个结果,比如对一个数组进行排序有很多种算法,可以并发启动所有的算法,但是对于指定的数组,第一个得到排序结果的算法就是最快的排序算法。
ThreadPoolExecutor类的invokeAny()方法接收到一个任务列表,然后运行任务,并返回第一个完成任务并且没有抛出异常任务的执行结果。
运行多个任务并处理所有结果
如果想要等待任务结束,可以使用如下两种方法:
如果任务执行结束,那么Future接口的isDone()方法返回true。
在调用shutdown()方法后,ThreadPoolExecutor类的awaitTermination()方法会见线程休眠,直到所有的任务执行结束。
ThreadPoolExecutor类还提供了一个方法,它允许发送一个任务列表给执行器,并等待列表中所有任务执行完成。
resultList=executor.invokeAll(taskList);
在执行器中周期性的执行任务:
使用ScheduledThreadPoolExecutor类来执行周期性的任务。
ScheduledFuture
package com.bh.recipe8.task;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* This class manage the execution of a ExecutableTaks. Overrides
* the done() method that is called when the task finish its execution
*
*/
public class ResultTask extends FutureTask<String> {
/**
* Name of the ResultTask. It's initialized with the name of the
* ExecutableTask that manages
*/
private String name;
/**
* Constructor of the Class. Override one of the constructor of its parent class
* @param callable The task this object manages
*/
public ResultTask(Callable<String> callable) {
super(callable);
this.name=((ExecutableTask)callable).getName();
}
/**
* Method that is called when the task finish.
*/
@Override
protected void done() {
if (isCancelled()) {
System.out.printf("%s: Has been cancelled\n",name);
} else {
System.out.printf("%s: Has finished\n",name);
}
}
}
package com.bh.recipe8.core;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import com.bh.recipe8.task.ExecutableTask;
import com.bh.recipe8.task.ResultTask;
/**
* Main class of the example. Creates five tasks that wait a random period of time.
* Waits 5 seconds and cancel all the tasks. Then, write the results of that tasks
* that haven't been cancelled.
*
*/
public class Main {
/**
* Main method of the class.
* @param args
*/
public static void main(String[] args) {
// Create an executor
ExecutorService executor=(ExecutorService)Executors.newCachedThreadPool();
//Create five tasks
ResultTask resultTasks[]=new ResultTask[5];
for (int i=0; i<5; i++) {
ExecutableTask executableTask=new ExecutableTask("Task "+i);
resultTasks[i]=new ResultTask(executableTask);
executor.submit(resultTasks[i]);
}
// Sleep the thread five seconds
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
// Cancel all the tasks. In the tasks that have finished before this moment, this
// cancellation has no effects
for (int i=0; i<resultTasks.length; i++) {
resultTasks[i].cancel(true);
}
// Write the results of those tasks that haven't been cancelled
for (int i=0; i<resultTasks.length; i++) {
try {
if (!resultTasks[i].isCancelled()){
System.out.printf("%s\n",resultTasks[i].get());
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
// Finish the executor.
executor.shutdown();
}
}
package com.bh.recipe8.task;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
/**
* This class implements the task of this example. It waits a random period of time
*
*/
public class ExecutableTask implements Callable<String> {
/**
* The name of the class
*/
private String name;
/**
* Constructor of the class
* @param name The name of the class
*/
public ExecutableTask(String name){
this.name=name;
}
/**
* Main method of the task. It waits a random period of time and returns a message
*/
@Override
public String call() throws Exception {
try {
Long duration=(long)(Math.random()*10);
System.out.printf("%s: Waiting %d seconds for results.\n",this.name,duration);
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException e) {
}
return "Hello, world. I'm "+name;
}
/**
* This method returns the name of the task
* @return The name of the task
*/
public String getName(){
return name;
}
}
在执行器中分离任务的启动和结果的处理
CompletionService类:该类有一个方法用来发送任务给执行器,还有一个方法为下一个已经执行结束的任务获取Future对象。
处理在执行器中被拒绝的任务
当我们想结束执行器的执行时,调用shutdown()方法来表示执行器应当结束。但是执行器只有等待正在运行的任务或者等待执行的任务结束后,才能真正的结束。
如果在shutdown()方法与执行器结束之间发送一个任务给执行器,这个任务会被拒绝,因为这个时间段执行器已不再接受任务了。ThreadPoolExecutor类提供了一套机制,当任务拒绝时使用这套机制来处理他们。
实现RejectedExe