Executor框架

Executor框架是指java 5中引入的一系列并发库中与executor相关的一些功能类,其中包括线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。并发编程的一种编程方式是把任务拆分为一些列的小任务,即Runnable,然后在提交给一个Executor执行,Executor.execute(Runnalbe) 。Executor在执行时使用内部的线程池完成操作。

 
 
一、创建线程池
Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
 
public static ExecutorService newFixedThreadPool(int nThreads) 
 
创建固定数目线程的线程池。
 
public static ExecutorService newCachedThreadPool() 
 
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
 
public static ExecutorService newSingleThreadExecutor() 
 
创建一个单线程化的Executor。
 
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
 
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
 
例子
 
1.固定线程创建线程池执行任务
    Executor executor = Executors.newFixedThreadPool(10);  
        
        Runnable task = new Runnable() {   
            @Override  
            public void run() {   
                System.out.println("task over");   
            }   
        };   
        Runnable task2 = new Runnable() {   
            @Override  
            public void run() {   
                System.out.println("task over2");   
            }   
        };
        executor.execute(task);   

executor.execute(task2);

 
2.定时周期执行任务代替timer类例子
    Executor executor = Executors.newScheduledThreadPool(10);   
        ScheduledExecutorService scheduler = (ScheduledExecutorService) executor;   
/**
 *         command:执行线程
        initialDelay:初始化延时
        period:两次开始执行最小间隔时间
        unit:计时单位
        */

scheduler.scheduleAtFixedRate(task2, 10, 10, TimeUnit.SECONDS);    

 
 
 
二.使用Callable,Future返回结果
Future<V>代表一个异步执行的操作,通过get()方法可以获得操作的结果,如果异步操作还没有完成,则,get()会使当前线程阻塞。FutureTask<V>实现了Future<V>和Runable<V>。Callable代表一个有返回值得操作。(一般Future结合callable使用)
 
 
futureTask的使用

单独使用Runnable时

无法获得返回值

单独使用Callable时

无法在新线程中(new Thread(Runnable r))使用,只能使用ExecutorService

Thread类只支持Runnable

FutureTask

实现了RunnableFuture,所以兼顾两者优点

既可以使用ExecutorService,也可以使用Thread

  1. Callable pAccount = new PrivateAccount();
  2. FutureTask futureTask = new FutureTask(pAccount);
  3. // 使用futureTask创建一个线程
  4. Thread thread = new Thread(futureTask);
  5. thread.start();

=================================================================

public interface Future<V> Future 表示异步计算的结果。
Future有个get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。

FutureTask是为了弥补Thread的不足而设计的,它可以让程序员准确地知道线程什么时候执行完成并获得到线程执行完成后返回的结果(如果有需要)。

FutureTask是一种可以取消的异步的计算任务。它的计算是通过Callable实现的,它等价于可以携带结果的Runnable,并且有三个状态:等待、运行和完成。完成包括所有计算以任意的方式结束,包括正常结束、取消和异常。

Future 主要定义了5个方法:

1) boolean cancel(boolean mayInterruptIfRunning):试图取消对此任务的执行。如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。此方法返回后,对 isDone() 的后续调用将始终返回 true。如果此方法返回 true,则对 isCancelled() 的后续调用将始终返回 true。 (取消当前任务或终止线程)

2)boolean isCancelled():如果在任务正常完成前将其取消,则返回 true。 
3)boolean isDone():如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。 
4)V get()throws InterruptedException,ExecutionException:如有必要,等待计算完成,然后获取其结果(futuretask获取Callable返回的结果)。

5)V get(long timeout,TimeUnit unit) throws InterruptedException,ExecutionException,TimeoutException:如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。

  1. public class FutureTask<V>  extends Object
  2. implements Future<V>, Runnable

FutureTask类是Future 的一个实现,并实现了Runnable,所以可通过Excutor(线程池) 来执行,也可传递给Thread对象执行。如果在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。 

Executor框架利用FutureTask来完成异步任务,并可以用来进行任何潜在的耗时的计算。一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。

 

futureTask和Callable的综合使用
 
例子1
        Callable<Integer> func = new Callable<Integer>(){   
            public Integer call() throws Exception {   
                System.out.println("inside callable");   
                Thread.sleep(1000);   
                return new Integer(8);   
            }          
        };         
        FutureTask<Integer> futureTask  = new FutureTask<Integer>(func);   
        Thread newThread = new Thread(futureTask);   
        newThread.start();   
          
        try {   
            System.out.println("blocking here");   
            Integer result = futureTask.get();   
            System.out.println(result);   
        } catch (InterruptedException ignored) {   
        } catch (ExecutionException ignored) {   

}   

 
 
例子2
1.多处理单元计算总和
 
ConcurrentCalculato.java为计算类
 
public class ConcurrentCalculato {
    
    
      private ExecutorService exec;   
        private int cpuCoreNumber;   
        private List<Future<Long>> tasks = new ArrayList<Future<Long>>();   
      
        // 内部类   
        class SumCalculator implements Callable<Long> {   
            private int[] numbers;   
            private int start;   
            private int end;   
      
            public SumCalculator(final int[] numbers, int start, int end) {   
                this.numbers = numbers;   
                this.start = start;   
                this.end = end;   
            }   
      
            /* (non-Javadoc)任务结束
             * @see java.util.concurrent.Callable#call()
             */
            public Long call() throws Exception {   
                Long sum = 0l;   
                for (int i = start; i < end; i++) {   
                    sum += numbers[i];   
                }   
                return sum;   
            }   
        }   
      
        public ConcurrentCalculato() {   
            cpuCoreNumber = Runtime.getRuntime().availableProcessors();   
            exec = Executors.newFixedThreadPool(cpuCoreNumber);   
        } 
        
        
        public Long sum(final int[] numbers) {   
            // 根据CPU核心个数拆分任务,创建FutureTask并提交到Executor   
            for (int i = 0; i < cpuCoreNumber; i++) {   
                int increment = numbers.length / cpuCoreNumber + 1;   
                int start = increment * i;   
                int end = increment * i + increment;   
                if (end > numbers.length)   
                    end = numbers.length;   
                SumCalculator subCalc = new SumCalculator(numbers, start, end);   
                FutureTask<Long> task = new FutureTask<Long>(subCalc);   
                tasks.add(task);   
                if (!exec.isShutdown()) {   
                    exec.submit(task);   
                }   
            }   
            return getResult();   
        }   
        
      //
 
/**  
* 迭代每个只任务,获得部分和,相加返回  
*   
* @return  
*/  
public Long getResult() {   
Long result = 0l;   
for (Future<Long> task : tasks) {   
    try {   
        // 如果计算未完成则阻塞   
        Long subSum = task.get();   
        result += subSum;   
    } catch (InterruptedException e) {   
        e.printStackTrace();   
    } catch (ExecutionException e) {   
        e.printStackTrace();   
    }   
}   
return result;   
}   
 
public void close() {   
exec.shutdown();   

}     

 
2.main方法里面的代码
    public static void main(String[] args) {  
    int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 10, 11 };   
        ConcurrentCalculato calc = new ConcurrentCalculato();   
        Long sum = calc.sum(numbers);   
        System.out.println(sum);   

calc.close();    

}
 
 
//待续这里。。。。
 
 
三、CompletionService
在刚在的例子中,getResult()方法的实现过程中,迭代了FutureTask的数组,如果任务还没有完成则当前线程会阻塞,如果我们希望任意字任务完成后就把其结果加到result中,而不用依次等待每个任务完成,可以使CompletionService。生产者submit()执行的任务。使用者take()已完成的任务,并按照完成这些任务的顺序处理它们的结果 。也就是调用CompletionService的take方法是,会返回按完成顺序放回任务的结果,CompletionService内部维护了一个阻塞队列BlockingQueue,如果没有任务完成,take()方法也会阻塞(阻塞当前统计的线程)。修改刚才的例子使用CompletionService:
 
Java代码 
 
public class ConcurrentCalculator2 {
    
    private ExecutorService exec;   
    private CompletionService<Long> completionService;   
  
  
    private int cpuCoreNumber;   
  
    // 内部类   
    class SumCalculator implements Callable<Long> {   
        private int[] numbers;   
        private int start;   
        private int end;   
  
        public SumCalculator(final int[] numbers, int start, int end) {   
            this.numbers = numbers;   
            this.start = start;   
            this.end = end;   
        }   
  
        /* (non-Javadoc)任务结束
         * @see java.util.concurrent.Callable#call()
         */
        public Long call() throws Exception {   
            Long sum = 0l;   
            for (int i = start; i < end; i++) {   
                sum += numbers[i];   
            }   
            return sum;   
        }   
    }   
    public ConcurrentCalculator2() {   
        cpuCoreNumber = Runtime.getRuntime().availableProcessors();   
        exec = Executors.newFixedThreadPool(cpuCoreNumber);   
        completionService = new ExecutorCompletionService<Long>(exec);   
  
  
    }   
  
    public Long sum(final int[] numbers) {   
        // 根据CPU核心个数拆分任务,创建FutureTask并提交到Executor   
        for (int i = 0; i < cpuCoreNumber; i++) {   
            int increment = numbers.length / cpuCoreNumber + 1;   
            int start = increment * i;   
            int end = increment * i + increment;   
            if (end > numbers.length)   
                end = numbers.length;   
            SumCalculator subCalc = new SumCalculator(numbers, start, end);    
            if (!exec.isShutdown()) {   
                completionService.submit(subCalc);   
  
  
            }   
               
        }   
        return getResult();   
    }   
  
    /**  
     * 迭代每个只任务,获得部分和,相加返回  
     *   
     * @return  
     */  
    public Long getResult() {   
        Long result = 0l;   
        for (int i = 0; i < cpuCoreNumber; i++) {               
            try {   
                Long subSum = completionService.take().get();   
                result += subSum;              
            } catch (InterruptedException e) {   
                e.printStackTrace();   
            } catch (ExecutionException e) {   
                e.printStackTrace();   
            }   
        }   
        return result;   
    }   
  
    public void close() {   
        exec.shutdown();   
    }   
    
 
}
 
四.ExecutorService
ExecutorService接口继承了Executor接口,定义了一些生命周期的方法
原型
  1. public interface ExecutorService extends Executor {
  2. void shutdown();
  3. List<Runnable> shutdownNow();
  4. boolean isShutdown();
  5. boolean isTerminated();
  6. boolean awaitTermination(long timeout, TimeUnit unit)
  7. throws InterruptedException;
  8. }  
     
 

本文,我们逐一分析里面的每个方法。

首先,我们需要创建一个任务代码,这段任务代码主要是随机生成含有10个字符的字符串

  1. /**
  2. * 随机生成10个字符的字符串
  3. * @author dream-victor
  4. *
  5. */
  6. public class Task1 implements Callable<String> {
  7. @Override
  8. public String call() throws Exception {
  9. String base = "abcdefghijklmnopqrstuvwxyz0123456789";
  10. Random random = new Random();
  11. StringBuffer sb = new StringBuffer();
  12. for (int i = 0; i < 10; i++) {
  13. int number = random.nextInt(base.length());
  14. sb.append(base.charAt(number));
  15. }
  16. return sb.toString();
  17. }
  18. }

然后,我们还需要一个长任务,这里我们默认是沉睡10秒,

  1. /**
  2. * 长时间任务
  3. *
  4. * @author dream-victor
  5. *
  6. */
  7. public class LongTask implements Callable<String> {
  8. @Override
  9. public String call() throws Exception {
  10. TimeUnit.SECONDS.sleep(10);
  11. return "success";
  12. }
  13. }

OK,所有前期准备完毕,下面我们就来分析一下ExecutorService接口中和生命周期有关的这些方法:

1、shutdown方法:这个方法会平滑地关闭ExecutorService,当我们调用这个方法时,ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。这里我们先不举例在下面举例。

2、awaitTermination方法:这个方法有两个参数,一个是timeout即超时时间,另一个是unit即时间单位。这个方法会使线程等待timeout时长,当超过timeout时间后,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。例如:

  1. ExecutorService service = Executors.newFixedThreadPool(4);
  2. service.submit(new Task1());
  3. service.submit(new Task1());
  4. service.submit(new LongTask());
  5. service.submit(new Task1());
  6. service.shutdown();
  7. while (!service.awaitTermination(1, TimeUnit.SECONDS)) {
  8. System.out.println("线程池没有关闭");
  9. }
  10. System.out.println("线程池已经关闭");

这段代码中,我们在第三次提交了一个长任务,这个任务将执行10秒沉睡,紧跟着执行了一次shutdown()方法,假设:这时ExecutorService被立即关闭,下面调用service.awaitTermination(1, TimeUnit.SECONDS)方法时应该返回true,程序执行结果应该只会打印出:“线程池已经关闭”。但是,真实的运行结果如下:

  1. 线程池没有关闭
  2. 线程池没有关闭
  3. 线程池没有关闭
  4. 线程池没有关闭
  5. 线程池没有关闭
  6. 线程池没有关闭
  7. 线程池没有关闭
  8. 线程池没有关闭
  9. 线程池没有关闭
  10. 线程池已经关闭

这说明我们假设错误,service.awaitTermination(1, TimeUnit.SECONDS)每隔一秒监测一次ExecutorService的关闭情况,而长任务正好需要执行10秒,因此会在前9秒监测时ExecutorService为未关闭状态,而在第10秒时已经关闭,因此第10秒时输出:线程池已经关闭。这也验证了shutdown方法关闭ExecutorService的条件。

3、shutdownNow方法:这个方法会强制关闭ExecutorService,它将取消所有运行中的任务和在工作队列中等待的任务,这个方法返回一个List列表,列表中返回的是等待在工作队列中的任务。例如:

  1. ExecutorService service = Executors.newFixedThreadPool(3);
  2. service.submit(new LongTask());
  3. service.submit(new LongTask());
  4. service.submit(new LongTask());
  5. service.submit(new LongTask());
  6. service.submit(new LongTask());
  7. List<Runnable> runnables = service.shutdownNow();
  8. System.out.println(runnables.size());
  9. while (!service.awaitTermination(1, TimeUnit.MILLISECONDS)) {
  10. System.out.println("线程池没有关闭");
  11. }
  12. System.out.println("线程池已经关闭");

这段代码中,我们限制了线程池的长度是3,提交了5个任务,这样将有两个任务在工作队列中等待,当我们执行shutdownNow方法时,ExecutorService被立刻关闭,所以在service.awaitTermination(1, TimeUnit.MILLISECONDS)方法校验时返回的是false,因此没有输出:线程池没有关闭。而在调用shutdownNow方法时,我们接受到了一个List,这里包含的是在工作队列中等待执行的任务,由于线程池长度为3,且执行的都是长任务,所以当提交了三个任务后线程池已经满了,剩下的两次提交只能在工作队列中等待,因此我们看到runnables的大小为2,结果如下:

  1. 2
  2. 线程池已经关闭

4、isTerminated方法:这个方法会校验ExecutorService当前的状态是否为“TERMINATED”即关闭状态,当为“TERMINATED”时返回true否则返回false。例如:

  1. ExecutorService service = Executors.newFixedThreadPool(3);
  2. service.submit(new Task1());
  3. service.submit(new Task1());
  4. service.submit(new LongTask());
  5. service.shutdown();
  6. System.out.println(System.currentTimeMillis());
  7. while (!service.isTerminated()) {
  8. }
  9. System.out.println(System.currentTimeMillis());

这段代码我们执行了两个正常的任务和一个长任务,然后调用了shutdown方法,我们知道调用shutdown方法并不会立即关闭ExecutorService,这时我们记录一下监测循环执行前的时间,在没有关闭前我们一直进入一个空循环中,直到 ExecutorService关闭后退出循环,这里我们知道长任务执行时间大约为10秒,我们看一下上述程序运行结果:

  1. 1303298818621
  2. 1303298828634
  3. 相差:10013毫秒,转换一下除以1000,得到相差大约10秒

这10秒正好是长任务执行的时间,因此在 ExecutorService正常关闭后isTerminated方法返回true。

5、isShutdown方法:这个方法在ExecutorService关闭后返回true,否则返回false。方法比较简单不再举例。

以上讨论是基于ThreadPoolExecutor的实现,不同的实现会有所不同需注意。

 
 
 
 

java多线程并发编程的更多相关文章

  1. Java 多线程并发编程一览笔录

    Java 多线程并发编程一览笔录 知识体系图: 1.线程是什么? 线程是进程中独立运行的子任务. 2.创建线程的方式 方式一:将类声明为 Thread 的子类.该子类应重写 Thread 类的 run ...

  2. 【收藏】Java多线程/并发编程大合集

    (一).[Java并发编程]并发编程大合集-兰亭风雨    [Java并发编程]实现多线程的两种方法    [Java并发编程]线程的中断    [Java并发编程]正确挂起.恢复.终止线程    [ ...

  3. java多线程并发编程与CPU时钟分配小议

    我们先来研究下JAVA的多线程的并发编程和CPU时钟振荡的关系吧 老规矩,先科普 我们的操作系统在DOS以前都是单任务的 什么是单任务呢?就是一次只能做一件事 你复制文件的时候,就不能重命名了 那么现 ...

  4. Java基础系列篇:JAVA多线程 并发编程

    一:为什么要用多线程: 我相信所有的东西都是以实际使用价值而去学习的,没有实际价值的学习,学了没用,没用就不会学的好. 多线程也是一样,以前学习java并没有觉得多线程有多了不起,不用多线程我一样可以 ...

  5. Java 多线程并发编程

    导读 创作不易,禁止转载! 并发编程简介 发展历程 早起计算机,从头到尾执行一个程序,这样就严重造成资源的浪费.然后操作系统就出现了,计算机能运行多个程序,不同的程序在不同的单独的进程中运行,一个进程 ...

  6. Java多线程并发编程/锁的理解

    一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等 ...

  7. Java多线程并发编程一览笔录

    线程是什么? 线程是进程中独立运行的子任务. 创建线程的方式 方式一:将类声明为 Thread 的子类.该子类应重写 Thread 类的 run 方法 方式二:声明实现 Runnable 接口的类.该 ...

  8. Java 多线程并发编程面试笔录一览

    知识体系图: 1.线程是什么? 线程是进程中独立运行的子任务. 2.创建线程的方式 方式一:将类声明为 Thread 的子类.该子类应重写 Thread 类的 run 方法 方式二:声明实现 Runn ...

  9. java多线程 并发 编程

    转自:http://www.cnblogs.com/luxiaoxun/p/3870265.html 一.多线程的优缺点 多线程的优点: 1)资源利用率更好 2)程序设计在某些情况下更简单 3)程序响 ...

随机推荐

  1. hdu 4198 Quick out of the Harbour(BFS+优先队列)

    题目链接:hdu4198 题目大意:求起点S到出口的最短花费,其中#为障碍物,无法通过,‘.’的花费为1 ,@的花费为d+1. 需注意起点S可能就是出口,因为没考虑到这个,导致WA很多次....... ...

  2. boost解析XML方法教程

    boost库在解析XML时具有良好的性能,可操作性也很强下地址有个简单的说明 http://blog.csdn.net/luopeiyuan1990/article/details/9445691 一 ...

  3. java中的public,protected,private权限修饰

    public和private基本没问题,主要是默认的和protected之间的区别 同一包中默认的和protected一样,所以来看看不同包的情况 看下如下代码,两个类位于不同包: public cl ...

  4. 版本问题 Java:Unsupported major.minor version 51.0 (unable to load class . . .

    导入别人的项目时报错  Java:Unsupported major.minor version 51.0 (unable to load class . . . 后发现错误是由于class编译器的J ...

  5. WPF星空效果

    效果 前阵子看到ay的蜘蛛网效果和知乎的登录页背景,觉得效果很酷.自己也想写一个.于是写着写着就变成这样了.少女梦幻的赶脚有木有.我这有着一颗少女心的抠脚大汉 实现思路 分为两个部分: 1.星星无休止 ...

  6. 年末整理git和svn的使用心得

    实习加毕业工作也一年多了,用过svn 也用过git,现在也是两种版本管理工具交替不同的项目再用. 趁年末放假之际,来梳理下. 对于SVN常用命令: .svn cp svn-trunk地址 svn-br ...

  7. XAF-列表视图编辑模式

    下面来看看XAF中列表有哪些编辑模式: 一.inline编辑 下图说明了WinForms和ASP.NET应用程序中的可编辑列表视图. 在win中,这个很友好,就像excel中编辑一样.5星功能^_^. ...

  8. [转载]关于shell脚本的基本语法

    关于shell脚本的基本语法 整理于:2014-03-31,何俭飞,mymladdr@sina.com 一.执行 1.shell脚本如果要被执行,一般地必须要有执行权限"x"(除了 ...

  9. 【转】PYTHON open/文件操作

    [注]虽是转载,但会在原文上有些修改! open/文件操作f=open('/tmp/hello','w')#open(路径+文件名,读写模式)#读写模式:r只读,r+读写,w新建(会覆盖原有文件),a ...

  10. 一个经典的js中关于块级作用域和声明提升的问题

    function functions(flag) { if (flag) { function getValue() { return 'a'; } } else { function getValu ...