java并发编程第四章 线程执行器(1)

来源:互联网 发布:美国的种族歧视 知乎 编辑:程序博客网 时间:2024/05/19 22:52

本篇博客是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;}@Overridepublic 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()方法将任务发送给TaskSystem.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;}@Overridepublic 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();  //调用该方法结束执行。否则线程会继续执行下去的。   }}

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

  













0 0
原创粉丝点击