• ExecutorService.invokeAny()和ExecutorService.invokeAll()的使用剖析


       ExecutorService是JDK并发工具包提供的一个核心接口,相当于一个线程池,提供执行任务和管理生命周期的方法。ExecutorService接口中的大部分API都是比较容易上手使用的,本文主要介绍下invokeAll和invokeAll方法的特性和使用。

    package tasks;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.TimeUnit;
    
    public class SleepSecondsCallable implements Callable<String>
    {
    	private String name;
    
    	private int seconds;
    
    	public SleepSecondsCallable(String name, int seconds)
    	{
    		this.name = name;
    		this.seconds = seconds;
    	}
    
    	public String call() throws Exception
    	{
    		System.out.println(name + ",begin to execute");
    
    		try
    		{
    			TimeUnit.SECONDS.sleep(seconds);
    		} catch (InterruptedException e)
    		{
    			System.out.println(name + " was disturbed during sleeping.");
    			e.printStackTrace();
    			return name + "_SleepSecondsCallable_failed";
    		}
    
    		System.out.println(name + ",success to execute");
    
    		return name + "_SleepSecondsCallable_succes";
    	}
    
    }
    

     这是一个通过睡眠来模拟的耗时任务,该任务是可中断/可终止的任务,能够响应中断请求。

    package tasks;
    
    import java.util.concurrent.Callable;
    
    public class ExceptionCallable implements Callable<String>
    {
    
    	private String name = null;
    
    	public ExceptionCallable()
    	{
    
    	}
    
    	public ExceptionCallable(String name)
    	{
    		this.name = name;
    	}
    
    	@Override
    	public String call() throws Exception
    	{
    		System.out.println("begin to ExceptionCallable.");
    
    		System.out.println(name.length());
    
    		System.out.println("end to ExceptionCallable.");
    
    		return name;
    	}
    
    }
    

     这是一个可能会在执行过程中,抛出空指针异常的任务。

    package tasks;
    
    import java.util.Random;
    import java.util.concurrent.Callable;
    
    public class RandomTenCharsTask implements Callable<String>
    {
    
    	@Override
    	public String call() throws Exception
    	{
    		System.out.println("RandomTenCharsTask begin to execute...");
    
    		StringBuffer content = new StringBuffer();
    
    		String base = "abcdefghijklmnopqrstuvwxyz0123456789";
    
    		Random random = new Random();
    
    		for (int i = 0; i < 10; i++)
    		{
    			int number = random.nextInt(base.length());
    			content.append(base.charAt(number));
    		}
    
    		System.out.println("RandomTenCharsTask complete.result=" + content);
    		return content.toString();
    	}
    
    }
    

     这是一个正常的短时的任务,产生10个随机字符组成的字符串。

    一、测试invokeAny()

    /**
     * 提交的任务集合,一旦有1个任务正常完成(没有抛出异常),会终止其他未完成的任务
     */
    public static void invokeAny1() throws Exception
    {
    	ExecutorService executorService = Executors.newFixedThreadPool(3);
    
    	List<Callable<String>> tasks = new ArrayList<Callable<String>>();
    
    	tasks.add(new SleepSecondsCallable("t1", 2));
    	tasks.add(new SleepSecondsCallable("t2", 1));
    
    	String result = executorService.invokeAny(tasks);
    
    	System.out.println("result=" + result);
    
    	executorService.shutdown();
    }
    

      程序的执行结果是:返回t2线程的执行结果t2_SleepSecondsCallable_succes,同时t1抛出java.lang.InterruptedException: sleep interrupted。

      也就说:一旦有1个任务正常完成(执行过程中没有抛异常),线程池会终止其他未完成的任务
     
    第二种情况,向线程池提交3个异常任务ExceptionCallable
    /**
    * 没有1个正常完成的任务,invokeAny()方法抛出ExecutionException,封装了任务中元素的异常
    * 
    */
    public static void invokeAny2() throws Exception
    {
    	ExecutorService executorService = Executors.newFixedThreadPool(3);
    
    	List<Callable<String>> tasks = new ArrayList<Callable<String>>();
    
    	tasks.add(new ExceptionCallable());
    	tasks.add(new ExceptionCallable());
    	tasks.add(new ExceptionCallable());
    
    	String result = executorService.invokeAny(tasks);
    
    	System.out.println("result=" + result);
    
    	executorService.shutdown();
    }
    

     程序执行结果是:调用invokeAny()报错 java.util.concurrent.ExecutionException: java.lang.NullPointerException。

     也就是说:如果提交的任务列表中,没有1个正常完成的任务,那么调用invokeAny会抛异常,究竟抛的是哪儿个任务的异常,无关紧要
     
    第三种情况:先提交3个异常任务,再提交1个正常的耗时任务
    /**
    * 有异常的任务,有正常的任务,invokeAny()不会抛异常,返回最先正常完成的任务
    */
    public static void invokeAny3() throws Exception
    {
    	ExecutorService executorService = Executors.newFixedThreadPool(3);
    
    	List<Callable<String>> tasks = new ArrayList<Callable<String>>();
    
    	tasks.add(new ExceptionCallable());
    	tasks.add(new ExceptionCallable());
    	tasks.add(new ExceptionCallable());
    	tasks.add(new ExceptionCallable());
    
    	tasks.add(new SleepSecondsCallable("t1", 2));
    
    	String result = executorService.invokeAny(tasks);
    
    	System.out.println("result=" + result);
    	executorService.shutdown();
    }
    

     程序执行结果是:不会抛出任何异常,打印出t2任务的返回结果。也就是说:invokeAny()和任务的提交顺序无关,只是返回最早正常执行完成的任务

    第四种情况,测试下使用限时版本的invokeAny(),主要功能与不限时版本的差别不大

    /**
     * 还没有到超时之前,所以的任务都已经异常完成,抛出ExecutionException<br>
     * 如果超时前满,还没有没有完成的任务,抛TimeoutException
     */
    public static void invokeAnyTimeout() throws Exception
    {
    	ExecutorService executorService = Executors.newFixedThreadPool(3);
    
    	List<Callable<String>> tasks = new ArrayList<Callable<String>>();
    
    	tasks.add(new ExceptionCallable());
    	tasks.add(new ExceptionCallable());
    	tasks.add(new ExceptionCallable());
    	tasks.add(new ExceptionCallable());
    
    	String result = executorService.invokeAny(tasks, 2, TimeUnit.SECONDS);
    
    	System.out.println("result=" + result);
    
    	executorService.shutdown();
    }
    

      程序执行结果是:抛出ExecutionException。这个其实很合理,也很好理解。如果在超时之前,所有任务已经都是异常终止,那就没有必要在等下去了;如果超时之后,仍然有正在运行或等待运行的任务,那么会抛出TimeoutException。

     最后我们来看下,JDK源码中ExecutorService.invokeAny的方法签名和注释
    /**
         * Executes the given tasks, returning the result
         * of one that has completed successfully (i.e., without throwing
         * an exception), if any do. Upon normal or exceptional return,
         * tasks that have not completed are cancelled.
         * The results of this method are undefined if the given
         * collection is modified while this operation is in progress.
         *
         * @param tasks the collection of tasks
         * @return the result returned by one of the tasks
         * @throws InterruptedException if interrupted while waiting
         * @throws NullPointerException if tasks or any of its elements
         *         are <tt>null</tt>
         * @throws IllegalArgumentException if tasks is empty
         * @throws ExecutionException if no task successfully completes
         * @throws RejectedExecutionException if tasks cannot be scheduled
         *         for execution
         */
        <T> T invokeAny(Collection<? extends Callable<T>> tasks)
            throws InterruptedException, ExecutionException;
    

     与我们测试结果一致,invokeAny()返回最先正常完成(without throwing exception)的任务直接结果;一旦有任务正常完成或者调用出现异常,线程池都会终止正在运行或等待运行(tasks that have not completed are cancelled)的任务。

    二、测试invokeAll()

      这个方法相对来说比较好理解,就是执行任务列表中的所有任务,并返回与每个任务对应的Futue。也就是说,任务彼此之间不会相互影响,可以通过future跟踪每一个任务的执行情况,比如是否被取消,是正常完成,还是异常完成,这主要使用Future类提供的API。

    public static void testInvokeAll() throws Exception
    {
    	ExecutorService executorService = Executors.newFixedThreadPool(5);
    
    	List<Callable<String>> tasks = new ArrayList<Callable<String>>();
    	tasks.add(new SleepSecondsCallable("t1", 2));
    	tasks.add(new SleepSecondsCallable("t2", 2));
    	tasks.add(new RandomTenCharsTask());
    	tasks.add(new ExceptionCallable());
    
    	// 调用该方法的线程会阻塞,直到tasks全部执行完成(正常完成/异常退出)
    	List<Future<String>> results = executorService.invokeAll(tasks);
    
    	// 任务列表中所有任务执行完毕,才能执行该语句
    	System.out.println("wait for the result." + results.size());
    
    	executorService.shutdown();
    
    	for (Future<String> f : results)
    	{
    		// isCanceled=false,isDone=true
    		System.out.println("isCanceled=" + f.isCancelled() + ",isDone="
    				+ f.isDone());
    
    		// ExceptionCallable任务会报ExecutionException
    		System.out.println("task result=" + f.get());
    	}
    }
    

      程序的执行结果和一些结论,已经直接写在代码注释里面了。invokeAll是一个阻塞方法,会等待任务列表中的所有任务都执行完成。不管任务是正常完成,还是异常终止,Future.isDone()始终返回true。通过Future.isCanceled()可以判断任务是否在执行的过程中被取消。通过Future.get()可以获取任务的返回结果,或者是任务在执行中抛出的异常。

    第二种情况,测试限时版本的invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)

    /**
     * 可以通过Future.isCanceled()判断任务是被取消,还是完成(正常/异常)<br>
     * Future.isDone()总是返回true,对于invokeAll()的调用者来说,没有啥用
     */
    public static void testInvokeAllTimeout() throws Exception
    {
    	ExecutorService executorService = Executors.newFixedThreadPool(5);
    
    	List<Callable<String>> tasks = new ArrayList<Callable<String>>();
    	tasks.add(new SleepSecondsCallable("t1", 2));
    	tasks.add(new SleepSecondsCallable("t2", 2));
    	tasks.add(new SleepSecondsCallable("t3", 3));
    	tasks.add(new RandomTenCharsTask());
    
    	List<Future<String>> results = executorService.invokeAll(tasks, 1,
    			TimeUnit.SECONDS);
    
    	System.out.println("wait for the result." + results.size());
    
    	for (Future<String> f : results)
    	{
    		System.out.println("isCanceled=" + f.isCancelled() + ",isDone="
    				+ f.isDone());
    	}
    
    	executorService.shutdown();
    
    }
    
    执行结果是: 
    
    wait for the result.4
     isCanceled=true,isDone=true
     isCanceled=true,isDone=true
     isCanceled=true,isDone=true
     isCanceled=false,isDone=true
    

     也就是说给定的超时期满,还没有完成的任务会被取消,即Future.isCancelled()返回true;在超时期之前,无论是正常完成还是异常终止的任务,Future.isCancelled()返回false。

    第三种情况,测试在等待invokeAll执行完成之前,线程被中断

    /**
     * 如果线程在等待invokeAll()执行完成的时候,被中断,会抛出InterruptedException<br>
     * 此时线程池会终止没有完成的任务,这主要是为了减少资源的浪费.
     */
    public static void testInvokeAllWhenInterrupt() throws Exception
    {
    	final ExecutorService executorService = Executors.newFixedThreadPool(5);
    
    	// 调用invokeAll的线程
    	Thread invokeAllThread = new Thread() {
    
    		@Override
    		public void run()
    		{
    			List<Callable<String>> tasks = new ArrayList<Callable<String>>();
    			tasks.add(new SleepSecondsCallable("t1", 2));
    			tasks.add(new SleepSecondsCallable("t2", 2));
    			tasks.add(new RandomTenCharsTask());
    
    			// 调用线程会阻塞,直到tasks全部执行完成(正常完成/异常退出)
    			try
    			{
    				List<Future<String>> results = executorService
    						.invokeAll(tasks);
    				System.out.println("wait for the result." + results.size());
    			} catch (InterruptedException e)
    			{
    				System.out
    						.println("I was wait,but my thread was interrupted.");
    				e.printStackTrace();
    			}
    
    		}
    	};
    
    	invokeAllThread.start();
    
    	Thread.sleep(200);
    
    	invokeAllThread.interrupt();
    
    	executorService.shutdown();
    
    }
    

    invokeAllThread 线程调用了ExecutorService.invokeAll(),在等待任务执行完成的时候,invokeAllThread被别的线程中断了。这个时候,

    ExecutorService.invokeAll()会抛出Java.lang.InterruptedException,任务t1和t2都被终止抛出java.lang.InterruptedException: sleep interrupted。

    也就是说一旦ExecutorService.invokeAll()方法产生了异常,线程池中还没有完成的任务会被取消执行

    参见:http://blog.csdn.net/baidu_23086307/article/details/51740852

              http://blog.csdn.net/lmj623565791/article/details/27250059

  • 相关阅读:
    Learn Goroutine
    Redis eviction policies
    Hungarian Algorithm
    Prime and Factors
    HDU 2642 Stars
    236. Lowest Common Ancestor of a Binary Tree
    Leetcode 96. Unique Binary Search Trees
    Search in Rotated Sorted Array
    ID Generator
    概率问题
  • 原文地址:https://www.cnblogs.com/moonandstar08/p/5827855.html
Copyright © 2020-2023  润新知