今天在背面试题的时候,背到了一个关于多线程的题目: runnable 和 callable 有什么区别?

参考答案是:

  • Runnable 接口run方法没有返回值;Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
  • Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
  • Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

Callable接口具体是如何和Future、FutureTask配合来获取异步执行的结果的呢?又发现了自己知识盲区。于是我看了多篇博客,自己手动敲了demo,写这篇博客总结了一下。

Future、FutureTask、Callable介绍

Future

Future表示一个异步计算的结果。它提供了一种访问异步操作的结果的方式,以便在主线程中获取异步计算的结果。

Future接口有5个方法:

  • cancel(boolean): 取消任务。传入的参数为表示若任务开始执行了,是否要尝试中断该线程。参数true为进行尝试中断,false为不进行中断。
  • isCanceled(): 获取该任务是否被中断了
  • isDone(): 该任务是否已经完成
  • get(): 获取该任务的返回值,若任务还未完成则会阻塞。
  • get(Long, TimeUnit): 获取任务的返回值,若任务还未完成则会阻塞。参数为指定的阻塞的最长时间,超时会抛出TimeoutException。

FutureTask

FutureTask类实现了Future和Runnable接口,是我们实现线程异步执行,并能获取执行结果的封装的实现类。FutureTask有两个构造方法:

  • FutureTask(Callable< V> callalbe): 通常的构造方法,Callable为异步任务,通过futureTask的get()方法可以阻塞地获取任务的返回值。
  • FutureTask(Runnable runnable, V result): runnable为异步任务,result保存了执行结果,当futureTask的get()方法返回的就是result对象。当然,我们若是关心任务是否执行完成而不关心任务的返回值时,可以使用该构造方法。

Callable

Callable是java多线程中一个函数式接口,它是在java1.5的时候出现的,它能够返回线程执行结果并且能够抛出线程执行过程中的异常。Callable的出现主要是为了弥补继承Thread或实现Runnable接口的线程执行完成后,无法获得线程执行后的结果。

函数式接口:一个接口中只包含有一个方法。接口可以使用@FunctionalInterface注解修饰,也可以不使用@FunctionalInterface注解修饰。

dc760dc2046342819466d9cfd009a437.png

看到Callable的源码我们会发现:Callable是个有泛型的接口,其中唯一的方法call有返回值,返回值的类型就是Callable传入的泛型。如果要实现一个异步任务,那么异步任务类实现Callable接口重写call方法,需要异步执行的代码就写在call方法中,同时返回执行结果。

实战演练

实现思路

当使用Callable和FutureTask来获取异步任务的返回值时,具体的步骤如下:

  1. 创建一个实现了Callable接口的任务。Callable接口包含一个call()方法,用于执行异步任务,并返回计算结果。

  2. 创建一个FutureTask对象,将Callable任务作为参数传入FutureTask的构造函数中。这样可以将Callable任务转换为一个FutureTask任务,使其具备异步计算的能力。

  3. 之后有两者创建线程的方式,一个是手动new Thread()创建线程执行方法,一个是配合线程池创建线程。

  4. 配合线程池使用: 创建一个ExecutorService对象,例如通过Executors工厂类创建一个线程池。

  5. 将FutureTask提交给ExecutorService执行,使用executor.submit(futureTask)方法。

  6. 在需要获取异步任务的返回值时,调用FutureTask的get()方法。如果异步任务尚未完成,get()方法会阻塞直到任务完成并返回结果。

  7. 最后记得关闭ExecutorService来释放资源,通过executor.shutdown()方法。

具体示例

使用Callable和FutureTask并行计算1+…+10和20+…+30的结果

手动new Thread()使用

	public static void main(String[] args) throws ExecutionException, InterruptedException {
// 这里用lambda匿名表达式新建了一个Callable的实现
        FutureTask<Integer> futureTask1 = new FutureTask<>(() -> {
            int res = 0;                                        
            for (int i = 0; i <= 10; i++) {
                res += i;
            }
            return res;
        });
        FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
            int res = 0;
            for (int i = 20; i <= 30; i++) {
                res += i;
            }
            return res;
        });
 // FutureTask实现了Runnable接口,所以可以作为参数构造Thread()
        Thread thread1 = new Thread(futureTask1);              
        Thread thread2 = new Thread(futureTask2);   
 // 实际上FutureTask的run方法就是在运行Callable的call方法           
        thread1.start();
        thread2.start();
// 先获取任务1的值,未完成则会阻塞;获取任务2的值同理
   
        System.out.println(futureTask1.get() + futureTask2.get());    
}

配合线程池使用

线程池为我们封装好了线程,使用线程池实现异步计算也更加优雅:

public static void main(String[] args) throws ExecutionException, InterruptedException {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                3,
                5,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
                // 这里线程池submit执行的是call方法,返回的是FutureTask对象
// 这里用lambda匿名表达式新建了一个Callable的实现     
        Future<Integer> future1 = threadPoolExecutor.submit(()->{   
            int res = 0;
                                       
            for (int i = 0; i <= 10; i++) {
                res += i;
            }
            return res;
        });
        Future<Integer> future2 = threadPoolExecutor.submit(()->{
            int res = 0;
            for (int i = 20; i <= 30; i++) {
                res += i;
            }
            return res;
        });

        System.out.println(future1.get() + future2.get());
    }

 

 

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐