五、Future&Callable

五、Future&Callable

5.1 Introduction :Runnable & Callable 区别:

1)Runnable没有返回值,Future & Callable 使线程具有返回值的功能。
2)Callable接口的call() 方法可以声明抛出异常,Runnable接口的run() 方法不可以抛出异常,只能try catch
执行完callable接口中的任务,返回值是用future接口获取的。
注意: callable()是一个函数式接口!!!而Future不是,Future有FutureTask这些比较常见的实现类

看下Callable的定义(已经将区别和联系说的很清楚了):
在这里插入图片描述

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *(计算一个结果,或者在出错时甩出一个异常)
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

比较有意思的一点是 ,假如Callable 的泛型传入的是Void,那么Callable其实和Runnable一样了,当然这样的写法其实有点无聊。

Callable<Void> callable = new Callable<Void>() {
    @Override
     public Void call() throws Exception {
         return null;
     }
 };

5.2 Future类

这是Future接口结构:

public interface Future<V>
1、A Future represents the result of an asynchronous computation.  
(Future代表一个异步运算的结果)

2、Methods are provided to check if the  computation is complete,
to wait for its completion, and to retrieve the result of the computation. 
(Future中提供了API来检查计算是否完成、等待计算完成、获取计算结果)

3、The   result can only be retrieved using method get when the computation has completed, 
blocking if necessary until it is ready. Cancellation is performed by the cancel method.
(Future的结果只能在计算完成后拿到,在计算完之前一直阻塞,通过Cancel方法还可以取消计算任务)

4、 Additional methods are provided to determine if the task completed normally or was cancelled.
 Once a computation has completed, the computation cannot be cancelled. If you would like to 
 use a Future for the sake of cancellability but not provide a usable result, you can declare types of the 
 form Future<?> and return null as a result of the underlying task.
(Future提供API来判断任务是正常完成还是取消了。已经完成计算的任务是不能取消的。
如果使用Future是为了利用这个类的“可取消性”,而不是利用它能返回结果的特性,那你可以将Future的
泛型声明为 ?,并且将这个任务返回值设为null)

下面是一种场景:
Sample Usage (Note that the following classes are all made-up.)
   interface ArchiveSearcher { String search(String target); }
  class App {
    ExecutorService executor = ...
    ArchiveSearcher searcher = ...
    void showSearch(final String target)
        throws InterruptedException {
      Future<String> future
        = executor.submit(new Callable<String>() {
          public String call() {
              return searcher.search(target);
          }});
          // 下面一行:子线程在 search,父线程也在计算,实际上做到了并行化
      displayOtherThings(); // do other things while searching
      try {
        displayText(future.get()); // use future
      } catch (ExecutionException ex) { cleanup(); return; }
    }
  }

也可以用 FutureTask 来替代上面的写法:
The FutureTask class is an implementation of Future that implements Runnable, and so may be 
executed by an Executor. For example, the above construction with submit could be replaced by:

   FutureTask<String> future =
    new FutureTask<String>(new Callable<String>() {
      public String call() {
        return searcher.search(target);
    }});
  executor.execute(future);

// 内存连续性效应:
Memory consistency effects: Actions taken by the asynchronous computation happen-before 
actions following the corresponding Future.get() in another thread.

5.3 get() 、isDone()

future.get() 是阻塞方法
future.isDone()不阻塞,不过要注意下用法,如官方注释所言。

Returns true if this task completed. Completion may be due to normal termination, an exception, or cancellation – in all of these cases, this method will return true.

这个方法只是判断任务是不是完成了。那什么叫完成呢?
正常的执行完、抛出异常、被取消了 --> 三种情况都叫已完成,所以不要误以为只有normal termination才是已完成。

看个十分简单的demo:

class MyCallable implements Callable<String> {
    private int age;

    public MyCallable(int age) {
        this.age = age;
    }

    @Override
    public String call() throws Exception {
        TimeUnit.SECONDS.sleep(3);
        return " age = " + age;
    }
}
public class MyFutureCallable {

    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable(10);
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Future<String> future = executorService.submit(myCallable);
        try {
            System.out.println(" begins at " + DateUtil.currentTime());
            System.out.println(future.get());
            System.out.println(" ends at " + DateUtil.currentTime());
            //等待时间刚好 3 秒
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

5.4 ExecutorService.submit(Runnable, T result)

在这里插入图片描述

ExecutorService.submit()有三种形式的传参。
注意:submit(Runnable ,T result)这种传参,result可作为执行结果的返回值,不需要用get()获取了。

Future 是个 interface,下面代码实际执行者是 FutureTask

public class MyFutureCallable {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Person person = new Person();
        System.out.println(" person ,in the beginning " + person); //  Person(age=0, name=null)
        Future<Person> future = executorService.submit(new PersonRunnable(person), person);

        System.out.println(" person ,in the middle " + person); //  Person(age=0, name=null)

        // 说明:在执行future.get()后,才将计算后的T 赋值给了传参的 T result。
        Person person1 = future.get();
        System.out.println(" person ,after the submit " + person); //  Person(age=10, name=ht)
        System.out.println(person == person1);
        System.out.println(person.hashCode() == person1.hashCode());
        // true 表示是同一个person对象

        executorService.shutdown();
    }
}

@AllArgsConstructor
class PersonRunnable implements Runnable{
    private Person person;

    @Override
    public void run() {
        person.setAge(10);
        person.setName("ht");
    }
}
@Data
class Person{
    private int age;
    private String name;
}

5.5 cancel(boolean mayInterruptIfRunning) & isCancelled()

根据官方文档呢,是这样解释的:
public abstract boolean cancel(boolean mayInterruptIfRunning)
Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
After this method returns, subsequent calls to isDone will always return true. Subsequent calls to isCancelled will always return true if this method returned true.

而关于这个传参:
mayInterruptIfRunning - true if the thread executing this task should be interrupted; otherwise, in-progress tasks are allowed to complete(true–>试图中断任务,false–>等任务执行完)

如何理解?
1、本API只是“试图”取消任务
2、“试图”而已,未必成功。在以下条件下就会“试图”失败:

  • a.任务已经完成了
  • b.任务已被取消过了
  • c. 由于某种原因取消不了了

3、尚未执行的任务若被能调用本方法,任务就不会执行了
4、若任务已经开始,那么传参mayInterruptIfRunning将会决定任务线程是否被置为interrupted状态,以期能成功中止任务
5、cancel()返回之后调用isDone ()总是返回true;如果cancel()返回true,再调用isCancelled ()总是返回true。

用大白话转述一下:
方法Future.cancel(boolean mayInterruptAfterRunning)返回值代表发送取消任务的命令是否已经成功完成。(注: 不是 代表是否已经成功取消,而是 “发送” 这个命令是否完成)(注:Future.cancel 而不是 ExecutorService.cancel!

那这个API有什么样的使用场景呢?
若线程正在运行,可以在代码中结合使用if(Thread.currentThread.isInterrupted)来确定是否已有中断状态,并通过抛出异常来强制中止线程。

public class MyCallableDemo {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<String> callable = () -> {
            try { // 注意一定要try catch 这个异常,主线程是无法打印这个异常的
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        throw new InterruptedException("中断了!");
                    }
                    System.out.println("假如中断了,就不执行了");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return " callable ";
        };
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        Future<String> future = executorService.submit(callable);
        TimeUnit.SECONDS.sleep(1);
        System.out.println("attempt to cancel the mission --> " + future.cancel(true)
                + "; has cancelled ? --> " + future.isCancelled());
        executorService.shutdown();
    }

}
/* ===输出结果===
java.lang.InterruptedException: 中断了!
	at juc.MyCallableDemo.lambda$main$0(MyCallableDemo.java:18)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
假如中断了,就不执行了
attempt to cancel the mission --> true; has cancelled ? --> true
 */

5.6 get(long timeout,TimeUnit unit)

在指定的最大时间里等待返回值,get()是阻塞的,timeout的时间内拿不到结果,
throw timeoutException()

5.7 handle exceptions

关于Callable任务中的异常处理, 先来看这个小小的demo:

public class MyCallableDemo {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<String> callable = () -> {
            System.out.println(" here begins the task ");
            int i = 1 / 0;  //此处抛出了异常,但是主线程并不感知
            System.out.println(" here ends the task ");
            return "callable";
        };

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Future<String> future = executorService.submit(callable);
//        future.get();   // 如果本行被注释了,则main  thread 不会输出异常
        executorService.shutdown();
        System.out.println(" main thread ends here ");
    }
}
    /* output ::
    main thread ends here
    here begins the task

     */

这个简单的demo,其实也就应了文章最初的说明:子线程的异常应该在子线程中去处理,而不应委托给父线程(父线程感知不到子线程的异常)

但这种说法不全对,再来看:

public class MyCallableDemo {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<String> callable = () -> {
            System.out.println(" here begins the task ");
            int i = 1 / 0;  //此处抛出了异常,干扰了主线程的正常执行
            System.out.println(" here ends the task ");
            return "callable";
        };

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Future<String> future = executorService.submit(callable);
        future.get(); // 此处抛出了异常,后面两句都不会再执行
        executorService.shutdown(); // 此处不执行
        System.out.println(" main thread ends here "); // 此处不执行 
    }
}
    /* output ::
  here begins the task 
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at juc.MyCallableDemo.main(MyCallableDemo.java:23)
Caused by: java.lang.ArithmeticException: / by zero
	at juc.MyCallableDemo.lambda$main$0(MyCallableDemo.java:16)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

     */

当执行到future.get()时,正常情况下应获取一个返回值,但是由于在子线程中抛出了异常,这个异常通过future.get()传递到了主线程,影响到了主线程的正常执行,尤其要注意的是executorService.shutdown();这样的关闭线程池资源的操作也没有执行!

所以呢,best practice应该是这样的:

public class MyCallableDemo {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<String> callable = () -> {
            try {
                System.out.println(" here begins the task ");
                int i = 1 / 0;
                System.out.println(" here ends the task ");
            } catch (Exception e) {
                e.printStackTrace();
//                throw e; // 可具体视情况要不要再抛出去
            }
            return "callable";
        };

        ExecutorService executorService = null;
        try {
            executorService = Executors.newFixedThreadPool(2);
            Future<String> future = executorService.submit(callable);
            future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            if (executorService != null) {
                executorService.shutdown();
            }
        }
        System.out.println(" main thread ends here ");
    }
}
    /* output ::
   here begins the task 
 main thread ends here 
java.lang.ArithmeticException: / by zero
	at juc.MyCallableDemo.lambda$main$0(MyCallableDemo.java:17)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
     */

怎么去理解这段代码呢?
总的来说就是“子线程的异常让子线程自己去处理,至于捕获后要不要重新抛出让父线程再感知到,需要视具体场景而定了”。

5.8 自定义拒绝策略(RejectedExecutionHandler)

接口:RejectedExecutionHandler --> 线程池关闭依然有任务要执行时,可做些处理。
ThreadPoolExecutor 本身也有默认的拒绝策略,也可自定义来灵活配置。
看看类图:
在这里插入图片描述
再看看官文的介绍:

public interface RejectedExecutionHandler
-->  A handler for tasks that cannot be executed by a ThreadPoolExecutor.
该方法只有一个接口: 
void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
--> Method that may be invoked by a ThreadPoolExecutor when execute cannot accept a task. 
This may occur when no more threads or queue slots are available because their bounds 
would  be exceeded, or upon shutdown of the Executor.
In the absence of other alternatives, the method may throw an unchecked RejectedExecutionException, 
which will be propagated (传播)to the caller of execute.
--> Parameters:
   r - the runnable task requested to be executed
   executor - the executor attempting to execute this task
--> Throws:
   RejectedExecutionException - if there is no remedy

大白话就是:
这个接口提供了当一个线程池(队列)由于线程数或者队列容量不够而需要“拒绝”这个任务时的处理方式。
需要尤其注意的是:设置了自定义的拒绝策略后,假如在关闭线程后,依然提交任务,可能导致线程池关闭失败。
而使用默认的拒绝策略,关闭线程后再提交任务,将会直接抛出异常。

public class MyRejectedExecutionDemo1 {

    public static void main(String[] args) {
        Callable<String> callable = () -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " running ");
            return name;
        };
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
        // 设置了 拒绝策略处理器
        RejectedExecutionHandler rejectedExecutionHandler = (r, executor1) -> {
            if (r instanceof FutureTask) { // 从这里我们可知:线程池中实际执行任务的其实是FutureTask这个类
                System.out.println(r + "  rejected !");
                try {
                    // 本例中,这里不会抛异常,拒绝的任务根本不会执行
                    System.out.println(((FutureTask) r).get());
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
        };
        executor.setRejectedExecutionHandler(rejectedExecutionHandler);

        executor.submit(callable);

        // 关闭了线程池资源
        executor.shutdownNow();
        // 但之后依然提交了任务,====所以线程池依然hold住,并没有关闭====
        executor.submit(callable);
    }
}
// output:
/*
    pool-1-thread-1 running 
    java.util.concurrent.FutureTask@5474c6c  rejected !

 !*/

5.9、execute()、submit()的区别

  1. 传参不同
    a)execute(Runnable r)
    b)submit(Runnable task ) --> 执行成功,Future.get() == null
    || submit(Callable task )
    || submit(Runnable task,T result) --> 利用T 对象去接收执行结果
  2. execute() --> 无返回结果;
    submit() --> 有返回值
  3. 异常处理:
  • execute() 执行过程中有异常在子线程中就直接打印堆栈,当然可以通过ThreadFactory方式 对任务线程setUncaughtExceptionHandler 捕获。
    (本质上,setUncaughtExeceptionHandler()是Thread的方法)
  • submit(new Callable () )可以显式捕获ExecutionException()

关于异常处理的N种姿势,在此说明:

public class DifferenceDemo {

    public static void main(String[] args) {

        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        try {
            executor.execute(() -> {
                int i = 1 / 0;
                System.out.println(" 1/0 ends !");
            });
            // execute --> 只能执行Runnable,异常默认会从子线程中直接抛出,而不是从父线程抛出
        } catch (Exception e) {
            System.out.println("    A  这里的异常   不会被  打印");
            e.printStackTrace();
        }

        executor.execute(() -> {
            try {
                int i = 1 / 0;
                System.out.println(" 1/0 ends !");
            } catch (Exception e) {
                System.out.println("    B 这里的异常     会被  打印");
                e.printStackTrace();
            }
        });

        try {
            Future<?> future1 = executor.submit(() -> {
                int i = 1 / 0;
                System.out.println(" 1/0 ends !");
            });
            // submit --> 可以执行 Runnable + Callable 两种任务,异常会被吞掉
        } catch (Exception e) {
            System.out.println("    C  这里的异常   不被  打印");
            e.printStackTrace();
        }

        try {
            Future<?> future2 = executor.submit(() -> {	// 这里不抛出异常
                int i = 1 / 0;
                System.out.println(" 1/0 ends !");
            });
            future2.get(); // 此处会抛出异常
            // 本质上::InterrupttedException、ExecutionException(这里直接用“大异常”Exception 代替
            了)是通过 future .get()  抓或抛的,而不是通过submit 本身。**

        } catch (Exception e) {
            System.out.println("    D   这里的异常   也会  被打印");
            e.printStackTrace();
        }

        executor.shutdown();
    }
}

下例中:若不 try catch,抛出的NPE无从知晓!还要注意的是:当你在Runnable里抓了异常处理,而不重新抛出的话(下面就是这种写法),future.get()时将不会获取Runnable中的异常。

	```
	Future<?> future = executor.submit(() -> {
	    try {
	        System.out.println(" i am submit ");
	        String a = null;
	        System.out.println(a.indexOf(0));
	    } catch (Exception e) {
	        e.printStackTrace();
	    }
	});
	future.get();
	```

因此:使用多线程的时候一定要记得try catch 或者 throw 的处理,否则连出错的栈轨迹都没有!!(巨坑!)

		```
		pool.setThreadFactory(new ThreadFactory() {
		    @Override
		    public Thread newThread(Runnable r) {
		        Thread thread = new Thread(r);
		        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
		            @Override
		            public void uncaughtException(Thread t, Throwable e) {
		                System.out.println(t.getName());
		                System.out.println(e);
		            }
		        });
		        return thread ;
		// 一定要在此处 return 掉 工厂中生成的 线程对象
		    }
		});
		
		pool.execute(() -> {
		    String a = null;
		    System.out.println(a.indexOf(1)); 
		// NPE!,但是程序会继续运行,而不是显式throwh出来并中断了程序。
		});
		System.out.println("--------");
		// 上面setXX可换成 lambda表达式
		
		```

5.9 future的缺点

Callable | Future 最大的好处是能够拿到线程执行的结果
Future的默认实现类:FutureTask
Future f = ThreadPoolExecutor.submit(callable )的时候,本质也是利用FutureTask来处理。
缺点:future.get() 拿结果是阻塞的,而且主线程并不能保证先完成的任务返回值被先拿到
怎么去理解呢?
就是说:主线程中有两个任务A 、B可以get 结果, A慢B快,如果是按A、B的顺序依次get(比如在循环里),那一定要等A get到结果后,B才能get 结果。所以你看 B虽然快,但还是不能先get()到结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值