java并发编程之线程执行器

1:简介

通常 使用java来开发一个简单的并发应用程序时 会创建一些Runnable对象然后创建对应的Thread对象来执行他们 但是 如果需要开发一个程序运行大量的并发任务 这个会显示出以下劣势:
必须实现所有与Thread对象管理的代码 比如线程的创建 结束以及结果获取 
需要为每一个任务创建一个Thread对象 如果需要执行大量的任务 这将大大的影响应用程序的处理能力
计算机的资源需要高效的进行控制和管理 如果创建过多的线程 将会导致系统负荷过重

自java5开始 java并发API提供了一套意在解决这些问题的机制 称之为执行框架 围绕着Executor接口和它的子接口ExecutorService 以及实现这两个接口的ThreadPoolExecutor类展开

这套机制分离了任务的创建和执行 通过使用执行器 仅需要RUnnable接口的对象 然后将这些对象发送给执行器即可  执行器通过创建所需要的线程  来负责这些Runnable对象的创建 实例化以及运行 但是执行器功能不限于此 它使用了线程池来提高应用性能 当发送一个任务给执行器时  执行器会尝试使用线程池中的线程来执行这个任务 避免了不断创建和销毁线程而导致系统性能下降
执行器另一个重要的优势是Callable接口 类似于Runnable接口 但是却提供了两个方面的增强
这个接口的主方法是call()方法 可以返回结果
当发送一个Callable对象给执行器时  将获得一个实现了Future接口的对象 可以使用这个对象来控制Callable对象的状态和结果

1.1:创建线程执行器

使用执行器框架的第一步是创建ThreadPoolExecutor对象 可以用ThreadPoolExecutor类提供的四个构造器 或者使用Executors工厂类来创建ThreadPoolExecutor对象 一旦有了执行器就可以将Runnable或者Callable对象发送给它取执行

接下来就使用ThreadPoolExecutor来实现模拟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() {
//在控制台上输出initDate属性和时间时间 即任务开始的时间
// TODO Auto-generated method stub
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 second\n",Thread.currentThread().getName(),name,duration);
TimeUnit.SECONDS.sleep(duration);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
//输出任务完成的时间
System.out.printf("%s: Task %s:finish on:%s\n",Thread.currentThread().getName(),name,new Date());

}
}
//创建一个Server类  它将执行通过执行器接收到的每一个任务


public class Server {
private ThreadPoolExecutor executor;
//通过Executor类来初始化ThreadPoolExecutor对象
public Server(){
executor=(ThreadPoolExecutor) Executors.newCachedThreadPool();
}
//接收Task对象作为参数 并将Task对象发送给执行器
public void excuteTask(Task task){
System.out.printf("Server:A new task has arrived\n");
executor.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();
}
}
创建主类


public class Main {


/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Server server=new Server();
for(int i=0;i<100;i++){
Task task=new Task("Task "+i);
server.excuteTask(task);
}
server.endServer();
}
}
工作原理:
Server类的构造器中创建ThreadPoolExecutor对象  ThreadPoolExecutor类有四个不同的构造器 但是由于这些构造器在使用上的复杂性 javaApi提供了Executors类,通过使用Executors工厂类的newCachedThreadPool方法创建了一个缓存线程池 这个方法返回ExecutorService对象 被强转为ThreadPoolExecutor类型  如果线程池需要执行新的任务 线程池就会创建新县城 如果线程所运行的任务执行完成后并且这个线程可用 那么缓存线程池将会重用这些线程  线程重用的有点事减少了创建新县城所花费的时间 
备注:仅当线程的数量是合理的或者线程只会运行很短的时间事  适合采用Executors工厂类的newCachedThreadPool方法来创建执行器
一点创建了执行器 就可以使用执行器的execute方法来发送Runnable或者Callable类型的任务  在例子中也打印了一些执行器相关的信息  
getPooSize返回执行器线程池中实际的线程数
getActiveCOunt方法返回执行器中正在执行任务的线程数
getCompletedTaskCount方法 返回执行器已经执行完成的数量

1.2:创建固定大小的线程执行器

当使用Executors类的newCachedThreadPool方法创建基本的ThreadPoolExecutor时  执行器运行过程中将碰到线程数量的问题 如果线程池中没有空闲的线程可用 那么执行器为接收到的每一个任务欻姑娘家一个新线程  当发送大量的任务给执行器并且任务需要等待较长的时间 系统会超过负荷 
为了避免这个问题 Executors工厂类提供了 一个方法来创建一个固定大小的线程执行器 这个执行器有一个线程数最大值 如果发送超过这个最大值的任务给执行器  执行器将不会再创建额外的线程 剩下的任务将被阻塞知道线程执行器有空闲的线程可用 这个特性可以保证执行器不会给应用程序带来性能不佳的问题

在上诉例子中修改:
修改Server类的构造器 使用newFixedThreadPool方法来创建执行器 
public server(){
   executor=(ThreadPoolExecutor)Executors.newFixedThreadPool(5);
}
修改executeTask方法 增加getTaskCount()方法来获取已经发送到执行器上的任务数
System.out.printf("Server: Task Count:%d\n",executor.getTaskCount());

1.3在执行器中执行任务并返回结果

执行器框架的优势之一是 可以运行并发任务并返回结果
Callable这个接口声明call方法 可以在这个方法里实现任务的具体逻辑操作 Callable接口是一个泛型接口 这就意味着必须声明call方法返回的数据类型
Future这个接口声明了一些方法来获取由Callable对象产生的结果 并管理他们的状态

接下来实现任务的返回结果 并在执行器中运行任务
创建FactoriakCakcukator类

public class FactorialCalculator implements Callable<Integer> {
    private Integer number;
    public FactorialCalculator(Integer number){
        this.number=number;
    }
    @Override
    public Integer call() throws Exception {
        int  result=1;
        if((number==0)||(number==1)){
            return result;
        }else{
            for(int i=2;i<number;i++){
                result*=i;
                TimeUnit.MILLISECONDS.sleep(20);
            }
        }
        System.out.printf("%s: %d\n",Thread.currentThread().getName(),result);
        return result;
    }
}
创建主类
public class Main {
    public static  void main(String args[]){
//创建线程执行器 执行器最多创建两个线程
        ThreadPoolExecutor executor= (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
//创建一个Future类型对象
        List<Future<Integer>> futureList=new ArrayList<Future<Integer>>();
        Random random=new Random();
        for(int i=0;i<10;i++){
            Integer number=random.nextInt(10);
            FactorialCalculator calculator=new FactorialCalculator(number);
//调用执行器的submit方法 发送FactorialCalculator给执行器 这个方法返回一个Future对象来管理任务和得到的结果
            Future<Integer> result=executor.submit(calculator);
            futureList.add(result);
        }
        do{
//输出任务完成数量
            System.out.printf("Main:Number of Completed Tasks:%d\n",executor.getCompletedTaskCount());
            for(int i=0;i<futureList.size();i++){
                Future<Integer> result=futureList.get(i);
//遍历10个Future对象 通过调用isDone方法来输出表示任务是不是完成
                System.out.printf("Main: Task %d: %s\n",i,result.isDone());
            }
            try{
                TimeUnit.MILLISECONDS.sleep(50);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }while(executor.getCompletedTaskCount()<futureList.size());//若执行器中完成的任务数量小于10 则一直重复执行这个循环
        System.out.printf("Main: Results\n");
        for(int i=0;i<futureList.size();i++){
            Future<Integer> result=futureList.get(i);
            Integer number=null;
            try{
//通过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();
    }
}
工作原理:
本例子中使用Callable接口来启动并发任务并返回结果 例子中返回Integer类型
通过submit发送一个Callable对象到执行器中去执行 这个方法接收Callable对象作为参数 并返回Future对象 这个对象可以控制任务的状态 可以取消任务和检查任务是不是已经完成  为了达到这个目的 可以使用isDone方法来检查任务是不是完成 通过call方法获取返回的结果 为了达到这个目的 可以使用get方法 这个方法一直等待知道Callable对象的call方法执行完成并返回结果

1.4运行多个任务并处理所有结果

执行器框架允许执行并发任务而不需要去考虑线程的创建和执行 它还提供了可以用来控制在执行器中执行的状态和获取任务运行结果的Future类
如果想要等待任务结束 可以使用如下两种方法
1:如果任务执行结束 那么Future接口的isDone方法将返回true
2:在调用shutdown方法后 ThreadPoolExecutor类的awaitTermination方法会将线程休眠 知道所有的任务执行结束
ThreadPoolExecutor类还提供了一个方法 它允许线程返回一个任务列表给执行器 并等待列表中所有任务执行完成 
接下来执行三个人物 它他们全部执行结束后打印出结果

创建Result类 用来存储并发任务产生的结果
public class Result {
    private String name;
    private int value;


    public String getName() {
        return name;
    }


    public void setName(String name) {
        this.name = name;
    }


    public int getValue() {
        return value;
    }


    public void setValue(int value) {
        this.value = value;
    }
}
创建Task类并实现Callable接口
public class Task  implements Callable<Result>{
    private String name;
    public Task(String name){
        this.name=name;
    }
    @Override
    public Result call() throws Exception {
        System.out.printf("%s: Starting\n",this.name);
        try{
            long duration= (long) (Math.random()*10);
            System.out.printf("%s: Waitting %d seconds for results.\n",this.name,duration);
            TimeUnit.SECONDS.sleep(duration);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        int value=0;
        for(int i=0;i<5;i++){
            value+=(int)(Math.random()*100);
        }
        Result result=new Result();
        result.setName(this.name);
        result.setValue(value);
        System.out.println(this.name+": Ends");
        return result;
    }
}
创建主类
public class Main {
    public static void main(String args[]) throws ExecutionException, InterruptedException {
        ExecutorService executor= Executors.newCachedThreadPool();
        List<Task> taskList=new ArrayList<Task>();
        for(int i=00;i<3;i++){
            Task task=new Task(i+"");
            taskList.add(task);
        }
        List<Future<Result>> futureList=null;
        try{
            futureList=executor.invokeAll(taskList);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executor.shutdown();
        System.out.println("Main: Printing the result");
        for(int i=0;i<futureList.size();i++){
            Future<Result> future=futureList.get(i);
            Result result=future.get();
            System.out.println(result.getName()+": "+result.getValue());
        }
    }
}
运行结果如下
0: Starting
2: Starting
1: Starting
0: Waitting 0 seconds for results.
2: Waitting 1 seconds for results.
1: Waitting 2 seconds for results.
0: Ends
2: Ends
1: Ends
Main: Printing the result
0: 254
1: 289
2: 169

工作原理:通过invokeAll方法等待所有任务的完成 这个方法接收一个Callable对象列表 并返回一个Future对象列表 在这个列表中 每一个任务对应一个Future对象
需要注意的是 在存储结果的列表中 用Future接口中的泛型参数的数据类型必须跟Callable接口的泛型数据相兼容
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值