本篇博客是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();  //调用该方法结束执行。否则线程会继续执行下去的。
   }
}



本篇实例比较简单,就不贴测试结果了。