Callable与Future、FutureTask的学习 & ExecutorServer 与 CompletionService 学习 & Java异常处理-重要

Callable是Java里面与Runnable经常放在一起说的接口。

 

Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务。

Callable的接口定义如下;

public interface Callable<V> { 

      V   call()   throws Exception; 

Callable和Runnable的区别如下:

I    Callable定义的方法是call,而Runnable定义的方法是run。

II   Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。

III  Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。  

 

Future 介绍

Future表示异步计算的结果,它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。Future的cancel方法可以取消任务的执行,它有一布尔参数,参数为 true 表示立即中断任务的执行,参数为 false 表示允许正在运行的任务运行完成。Future的 get 方法等待计算完成,获取计算结果。

 

下面四个头文件一般是一起用的:

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;

* Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。

 * Callable和Runnable有几点不同:

 * (1)Callable规定的方法是call(),而Runnable规定的方法是run().

 * (2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。

 * (3)call()方法可抛出异常,而run()方法是不能抛出异常的。

 * (4)运行Callable任务可拿到一个Future对象,

 * Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。

 * 通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。

 

示例代码如下:

package com.company;

import java.util.concurrent.*;

public class Main {

    public static class MyCallable implements Callable {
        private int flag = 0;
        public MyCallable(int flag) {
            this.flag = flag;
        }

        @Override
        public Object call() throws Exception {
            if (this.flag == 0) {
                return "flag == 0";
            }
            else if (this.flag == 1) {
                try {
                    while (true) {
                        System.out.println("looping");
                        Thread.sleep(2000);
                    }
                } catch (InterruptedException e) {
                    System.out.println("Interrupted");
                }
                return false;
            }
            else {
                throw new Exception("Bad flag value!");
            }
        }

    }

    public static void main(String[] args) {

        MyCallable task0 = new MyCallable(0);
        MyCallable task1 = new MyCallable(1);
        MyCallable task2 = new MyCallable(2);

        ExecutorService es = Executors.newFixedThreadPool(3);
        try {
            Future future0 = es.submit(task0);
            System.out.println("task0: " + future0.get());

            Future future1 = es.submit(task1);
            Thread.sleep(5000);
            System.out.println("task1 cancelled: " + future1.cancel(true)); // 注意这时候Task在Sleep,是被Interrupted了

            Future future2 = es.submit(task2);
            System.out.println("task2: " + future2.get());

        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        es.shutdown();

        System.out.println();

    }

}

 

运行的结果是:

task0: flag == 0


looping
looping
looping
Interrupted


java.util.concurrent.ExecutionException: java.lang.Exception: Bad flag value!
task1 cancelled: true
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at com.company.Main.main(Main.java:89)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.lang.Exception: Bad flag value!
    at com.company.Main$MyCallable.call(Main.java:67)
    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)

 

可以看出上面的三个部分,分别对应了3个flag的线程执行。

同时也复习了 ExecutorService 的内容。

ExecutorService实际上是一个线程池的管理工具:         ExecutorService executorService = Executors.newCachedThreadPool();         ExecutorService executorService = Executors.newFixedThreadPool(3);         ExecutorService executorService = Executors.newSingleThreadExecutor();

 

用CompletionService的例子可以看这里:

package com.company;


import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;

public class Main {

    public static void main(String[] args) {

        ExecutorService es = Executors.newCachedThreadPool();
        CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(es);

        for (int i=0; i<5; i++) {
            final int taskId = i;
            cs.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return taskId;
                }
            });
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int i=0; i<5; i++) {
            // 注意,用了CompletionService之后,异常获取是放在这里
            try {
                //System.out.println("i" + i);
                System.out.println(cs.take().get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        es.shutdown();

        System.out.println();

    }

}

 

运行结果:

1
0
2
3
4

注意,这个顺序是按照完成顺序排列的。

其实,这个也是先线程运行,然后sleep,最后get结果的,可以见文章再后面部分的详解。

 

Runnable

它只有一个run()函数,用于将耗时操作写在其中,该函数没有返回值。作为参数传给Thread构造函数,Thread类在调用start()函数后就是执行的是Runnable的run()函数(异步,立即返回)。Runnable的声明如下:

public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

 

Callable 

Callable与Runnable的功能大致相似,Callable中有一个call()函数,但是call()函数有返回值,而Runnable的run()函数不能将结果返回给客户程序。Callable的声明如下 :

可以看到,这是一个泛型接口,call()函数返回的类型就是客户程序传递进来的V类型

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;
}

注意上面的 throws Exception,这是重要的区别。你覆盖其父类方法时不能抛出父类没有声明的可捕获异常。所以你继承的Runnable的run方法中不能抛出任何的exception,而继承的Callable的call方法里面可以抛出异常。

还有注意,抛出抛出unchecked exception(Error或RuntimeException或子类),和抛出异常是两码事情。因为unchecked exception即RuntimeException(运行时异常)是不需要try…catch…或throws 机制去处理的异常,就好像空指针异常一样,你可以在任何的代码中进行抛出或捕获。但是捕获方式跟try…catch…或throws 机制不一样。

对于如何捕获unchecked exception,可以实现Thread.UncaughtExceptionHandler中的uncaughtException来捕获:

代码如下:

package com.company;

import java.util.concurrent.*;

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("Caught " + e);
    }
}

class HandleThread implements ThreadFactory {

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        return t;
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("About to throw RuntimeException");
        throw new RuntimeException();
    }
}

public class Main {


    public static void main(String[] args) {

        ExecutorService es = Executors.newCachedThreadPool(new HandleThread());
        es.execute(new Thread(new MyRunnable()));

        es.shutdown();

        System.out.println();

    }

}

输出结果:

About to throw RuntimeException

Caught java.lang.RuntimeException

如果把上面的RuntimeException换成Error,如下:

throw new Error();

输出结果:
About to throw RuntimeException
Caught java.lang.Error

不太明白,为什么RuntimeException多了一个空行

如果换成除0的运算式子,如下:

int a = 12 / 0;

输出如下:
About to throw RuntimeException

Caught java.lang.ArithmeticException: / by zero

 

如果 Executors.newCachedThreadPool 的参数里面不加上 ThreadFactory,那么这两种运行时错误都是抓不住的。

 

当然了,也可以对Thread直接设置,如下:

class ErrHandler implements UncaughtExceptionHandler {
    ...
}

ThreadA a = new ThreadA();
handle = new ErrHandler();
a.setUncaughtExceptionHandler(handle);// 加入定义的ErrHandler
a.start();

 

Future

Executor就是Runnable和Callable的调度容器,Future就是对于具体的Runnable或者Callable任务的执行结果进行

取消、查询是否完成、获取结果、设置结果操作get方法会阻塞,直到任务返回结果(Future简介)。

看下面函数的说明,应该能看出大概:

/**
* @see FutureTask
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> The result type returned by this Future's <tt>get</tt> method
 */
public interface Future<V> {

    /**
     * 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 <tt>cancel</tt> is called,
     * this task should never run.  If the task has already started,
     * then the <tt>mayInterruptIfRunning</tt> parameter determines
     * whether the thread executing this task should be interrupted in
     * an attempt to stop the task.     *
     */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
     * Returns <tt>true</tt> if this task was cancelled before it completed
     * normally.
     */
    boolean isCancelled();

    /**
     * Returns <tt>true</tt> if this task completed.
     *
     */
    boolean isDone();

    /**
     * Waits if necessary for the computation to complete, and then
     * retrieves its result.
     *
     * @return the computed result
     */
    V get() throws InterruptedException, ExecutionException;

    /**
     * Waits if necessary for at most the given time for the computation
     * to complete, and then retrieves its result, if available.
     *
     * @param timeout the maximum time to wait
     * @param unit the time unit of the timeout argument
     * @return the computed result
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

注意上面的TimeUnit,是这样子的,能够控制前面的timeout数字代表的是天、时还是秒等。通过如下定义:

TimeUnit timeUnit =TimeUnit.DAYS;
就代表了,前面timeout是1就1天,前面timeout是2就2天。

 

FutureTask

FutureTask则是一个RunnableFuture<V>,而RunnableFuture实现了Runnbale又实现了Futrue<V>这两个接口,

public class FutureTask<V> implements RunnableFuture<V>  

RunnableFuture

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

另外它还可以包装Runnable和Callable<V>, 由构造函数注入依赖。

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

可以看到,Runnable注入会被Executors.callable()函数转换为Callable类型,即FutureTask最终都是执行Callable类型的任务。该适配函数的实现如下 :

    public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }

RunnableAdapter适配器

    /**
     * A callable that runs given task and returns given result
     */
    static final class RunnableAdapter<T> implements Callable<T> {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

由于FutureTask实现了Runnable,因此它既可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行。

并且还可以直接通过get()函数获取执行结果,该函数会阻塞,直到结果返回。因此FutureTask既是Future、

Runnable,内部又包装了Callable( 如果是Runnable最终也会被转换为Callable ), 它是这两者的合体。

 

来一段代码示例吧:

public class Main {
    public static void main(String[] args) {

        ExecutorService es = Executors.newCachedThreadPool();

        FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("hi hi");
                return null;
            }
        });
        es.submit(futureTask);

        es.shutdown();
        System.out.println();
    }
}

运行结果是能够打印。注意上面的submit接口接受的参数是Runnable和Callable。这里FutureTask是作为Runnable来调用的。

而execute接口只接受Runnable。

 

关于 submit 和 execute的区别如下:

1、接收的参数不一样,刚刚说了。

 

2、submit有返回值,而execute没有

Method submit extends base method Executor.execute by creating and returning a Future that can be used to cancel execution and/or wait for completion. 

用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么。然后我就可以把所有失败的原因综合起来发给调用者。

从之前的例子应该可以看出,返回了Future实例,而通过Future可以get和cancel操作。

Future future2 = es.submit(task2);

 

3、submit方便Exception处理

There is a difference when looking at exception handling. If your tasks throws an exception and if it was submitted with execute this exception will go to the uncaught exception handler (when you don't have provided one explicitly, the default one will just print the stack trace to System.err). If you submitted the task with submit any thrown exception, checked or not, is then part of the task's return status. For a task that was submitted with submit and that terminates with an exception, the Future.get will rethrow this exception, wrapped in an ExecutionException.

意思就是如果你在你的task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,
那么就需要用到submit,通过捕获Future.get抛出的异常。

上面这个尤其要关注,是调用get的时候,才会抛出异常。如果不调用get,那其实线程还是执行的,但是不会抛出异常。

  再上面的例子,改一下如下:

package com.company;

import java.util.concurrent.*;

public class Main {

    public static class MyCallable implements Callable {
        private int flag = 0;
        public MyCallable(int flag) {
            this.flag = flag;
        }

        @Override
        public Object call() throws Exception {
            if (this.flag == 0) {
                return "flag == 0";
            }
            else if (this.flag == 1) {
                try {
                    while (true) {
                        System.out.println("looping");
                        Thread.sleep(4000);
                    }
                } catch (InterruptedException e) {
                    System.out.println("Interrupted");
                }
                return false;
            }
            else {
                System.out.println("About to throw Exception");
                throw new Exception("Bad flag value!");
            }
        }

    }

    public static void main(String[] args) {

        MyCallable task0 = new MyCallable(0);
        MyCallable task1 = new MyCallable(1);
        MyCallable task2 = new MyCallable(2);

        ExecutorService es = Executors.newFixedThreadPool(3);
        try {
            Future future0 = es.submit(task0);
            System.out.println("task0: " + future0.get());

            Future future1 = es.submit(task1);
            Thread.sleep(5000);
            System.out.println("task1 cancelled: " + future1.cancel(false)); 

            Future future2 = es.submit(task2);
            //System.out.println("task2: " + future2.get());

        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        es.shutdown();

        System.out.println();

    }

}

注意,其中两个改动点,已飘红:

1. 第二个Callable返回的Future的cancel参数用false;

2. 第三个抛出异常的Callable返回的Future不再调用get函数,看是否有异常抛出。在抛出异常前,也加了一个打印。

 

运行结果如下:

task0: flag == 0
looping
looping
task1 cancelled: true
About to throw Exception looping
looping

可以看到2点:

1. 循环一直在继续,也就是cancel(false)压根没能够停止住线程。意思是等线程运行结束了,状态置为cancelled?

2. 没有get就没有实际抛出异常。

 

 

对于之前那个CompletionService的例子,增加了一些改动如下:

package com.company;


import java.util.concurrent.*;


public class Main {

    public static void main(String[] args) {

        ExecutorService es = Executors.newCachedThreadPool();
        CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(es);

        for (int i=0; i<5; i++) {
            final int taskId = i;
            cs.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    System.out.println("Run thread " + taskId);
                    return taskId;
                }
            });
        }

        try {
            System.out.println("Sleeping");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int i=0; i<5; i++) {
            // 注意,用了CompletionService之后,异常获取是放在这里
            try {
                //System.out.println("i" + i);
                System.out.println(cs.take().get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        es.shutdown();

        System.out.println();

    }

}

运行了之后,得到结果:

Run thread 0
Run thread 1
Run thread 2
Run thread 3
Sleeping
Run thread 4
(此处等待数秒)
0
1
2
3
4

证明了,CompletionService也是先运行,然后get的时候才获得结果的。

 

也就是说,submit和execute在启动线程方面,没有区别,都是即时执行的

 

再复习一下异常的情况,如果一道面试题是这样的:

一个线程运行时发生异常会怎样?

那么,回答,要区分普通异常,还是RuntimeException(包括其子类和Error),

如果是普通异常,Runnable无法抛出,但是Callable可以抛出,应该通过在Future实例的get方法调用的地方接住;

如果是RuntimeException,如下:

简单的说,如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。
当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler,
并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。

需要特别注意的是,一个线程的异常只会终止当前线程,而对其他线程和主线程是没有任何影响的。比如如下例子:

package com.company;

import java.util.concurrent.*;

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("Caught " + e);
    }
}

class HandleThread implements ThreadFactory {

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        return t;
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("About to throw RuntimeException");
        throw new RuntimeException();
    }
}

public class Main {


    public static void main(String[] args) {

        ExecutorService es = Executors.newCachedThreadPool();//new HandleThread());
        es.execute(new Thread(new MyRunnable()));

        es.shutdown();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Done");

    }

}

调用结果,当子线程出现RuntimeException并抛出未接住的时候,停止的只有子线程,而主线程是不受影响的。

About to throw RuntimeException
Exception in thread "pool-1-thread-1" java.lang.RuntimeException
    at com.company.MyRunnable.run(Main.java:28)
    at java.lang.Thread.run(Thread.java:745)
    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)

(此处有数秒主线程Sleep停顿)

Done

 

注意领悟下面这段话:

在java多线程程序中,所有线程都不允许抛出未捕获的checked exception(比如sleep时的InterruptedException),(注:Callable可以,另谈)
也就是说各个线程需要自己把自己的checked exception处理掉。
这一点是通过java.lang.Runnable.run()方法声明(因为此方法声明上没有throw exception部分)进行了约束。

但是线程依然有可能抛出unchecked exception(如运行时异常),当此类异常跑抛出时,线程就会终结,而对于主线程和其他线程完全不受影响
且完全感知不到某个线程抛出的异常(也是说完全无法catch到这个异常)。(注:可以实现UncaughtExceptionHandler并且对本线程加上这个handler来处理)
JVM的这种设计源自于这样一种理念:“线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。”

基于这样的设计理念,在Java中,线程方法的异常(无论是checked还是unchecked exception),
都应该在线程代码边界之内(run方法内)进行try catch并处理掉.换句话说,我们不能捕获从线程中逃逸的异常。

 

 

 

    原文作者:blcblc
    原文地址: https://www.cnblogs.com/charlesblc/p/6171103.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞