本篇博客是java并发编程实战手册中的第四章前三节,主要说明并发框架----执行器
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实现决定。
实例代码:
1.Task 模拟web任务
public class Task implements Runnable{
private Date initDate;
private String name;
public Task(String name) {
initDate = new Date();
this.name = name;
}
@Override
public void run() {
System.out.printf("%s: Task %s: created on: %s\n",Thread.currentThread().getName(),name,initDate);
System.out.printf("%s Task %s : started on %s\n",Thread.currentThread().getName(),name,new Date());
try{
Long duration = (long)(Math.random() * 10);
System.out.printf("%s: Task %s: Doing a task during %d seconds\n",Thread.currentThread().getName(),name,duration);
TimeUnit.SECONDS.sleep(duration);
}catch(Exception e){
e.printStackTrace();
}
System.out.printf("%s Task %s : Finished on: %s\n",Thread.currentThread().getName(),name,new Date());
}
}
2.Server 模拟服务器处理
public class Server {
private ThreadPoolExecutor executor;
public Server(){
//创建具有缓存功能的线程池
executor = (ThreadPoolExecutor)Executors.newCachedThreadPool();
}
public void executeTask(Task task){
System.out.println("Server: A new task has arrived!");
executor.execute(task); //调用执行器的execute()方法将任务发送给Task
System.out.printf("Server: Pool Size : %d\n",executor.getPoolSize());
System.out.printf("Server: Active Count: %d\n",executor.getActiveCount());
System.out.printf("Server: Completed Tasks: %d\n",executor.getCompletedTaskCount());
}
public void endServer(){
executor.shutdown();
//启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。
}
}
3.Main 测试类
public class Main {
public static void main(String[] args) {
Server server = new Server();
for(int i =0 ;i< 100;i++){
Task task = new Task("Task "+i);
server.executeTask(task);
}
server.endServer();
}
}
2.创建固定大小的线程执行器
Executor工厂类提供了一个方法来创建一个固定大小的线程执行器。这个执行器有一个线程数量的最大值,
如果发送超过这个最大值的任务
给执行器,执行器将不再创建额外的线程,剩下的任务将被阻塞到执行器有空闲的线程可用。
这个特性可以保证执行器不会给应用程序带来性能问题。
executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(5); //创建有五个线程的线程池
这里使用了一些ThreadPoolExecutor中的方法:
1.getPoolSize():返回执行器中线程的实际数量
2.getActiveCount():返回执行器正在执行任务的线程数量。
说明:Executors工厂类也提供newSingleThreadExecutor()方法。这是一个创建固定大小的线程执行器的一个极端场景。
将创建一个只有单个线程的执行器。因此
这个执行器在同一时间执行一个任务。
本节测试类与上一节相同修改Server类的构造函数即可创建固定大小的线程执行器
public Server(){
//创建具有缓存功能的线程池
executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(5);
}
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对象管理线程返回的结果。
1.求n的阶乘
/**
*
* @author fcs
* @date 2015-5-5
* 描述:在执行器中执行任务并返回结果
* 说明:多线程执行任务可以返回结果需要借助两个接口
* 1.Callable接口,2.Future接口
* 这里实现Callable接口
*/
public class FactorialCalculator implements Callable<Integer>{
private Integer number;
public FactorialCalculator(Integer number) {
super();
this.number = number;
}
@Override
public Integer call() throws Exception {
int result = 1;
if((number ==0)||(number ==1)){
result = 1;
}else{
for(int i =2; i<= number;i ++){
result *= result;
TimeUnit.MILLISECONDS.sleep(20);
}
}
System.out.printf("%s: %d\n",Thread.currentThread().getName(),result);
return result;
}
}
2.Main测试类
public class Main {
public static void main(String[] args) {
ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(2);
List<Future<Integer>> resultList = new ArrayList<Future<Integer>>();
Random random = new Random();
for(int i =0;i< 10;i++){
Integer number = random.nextInt(10);
FactorialCalculator fc = new FactorialCalculator(number);
//这个方法返回一个Future<Integer>对象来管理任务和得到的最终结果
Future<Integer> result = executor.submit(fc);
resultList.add(result);
}
//创建一个do循环来监控执行器的状态
do{
System.out.printf("Main: Number of Completed Tasks: %d\n",executor.getCompletedTaskCount());
for(int i =0;i < resultList.size();i++ ){
Future<Integer> result = resultList.get(i);
//通过isDone()方法可以检查任务是否完成
System.out.printf("Main: Task %d: %s\n",i,result.isDone());
}
try{
TimeUnit.MILLISECONDS.sleep(50);
}catch(Exception e){
e.printStackTrace();
}
}while(executor.getCompletedTaskCount() < resultList.size());
System.out.println("Main result ");
for(int i =0 ;i< resultList.size();i++){
Future<Integer>result = resultList.get(i);
Integer number = null;
try {
//对于每个Future来讲,通过get方法将得到由任务返回的Integer对象
number = result.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.printf("Main: Task %d: %d\n",i,number);
}
executor.shutdown(); //调用该方法结束执行。否则线程会继续执行下去的。
}
}
本篇实例比较简单,就不贴测试结果了。