java—Runnable接口与Callable接口的区别

写在前面

Java中创建线程的方式有很多种(本质上只有一种),其中,使用 Runnable 接口和 Callable 接口都可以实现线程任务的定义,但二者在使用上有一些区别,比如是否有返回值、是否能抛出异常等。本文从接口定义、区别、示例代码等方面,对两个接口进行介绍。

接口定义

Runnable接口:

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

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

注意到:

① 二者均有FunctionalInterface注解,即二者都是函数式接口,都可以使用lambda表达式简化对象的创建。

② Callable接口是一个泛型接口,有一个泛型参数V作为抽象方法call的返回值。

二者区别

  1. Runnable接口是一个普通接口,Callable接口是一个泛型接口,有一个泛型参数V。
  2. Runnable接口中的run方法没有返回值,Callable接口中的Call方法有返回值。
  3. Runnable接口中的run方法没有抛出异常,所有的可检查异常(Checked Exception)都需要在run方法中自行处理,而 Callable接口的Call方法使用throws抛出了异常,可以在Call方法的外部捕捉到异常进行处理。
  4. 在创建线程时,Runnable 可以直接传入 Thread 类的构造方法,而 Callable 需要通过 FutureTask 包装后再交给线程或线程池执行。

示例代码

创建两个线程,分别输出1-5。

Runnable接口:

public class RunnableTest {
    public static void main(String[] args) {
        // 使用匿名内部类的方式创建实现了Runnable接口的实例
        Runnable task = new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                for(int num = 1; num <= 5; num++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(threadName + ":" + num);
                }
            }
        };
        // 创建两个线程执行任务
        Thread t1 = new Thread(task, "t1");
        Thread t2 = new Thread(task, "t2");
        t1.start();
        t2.start();
    }
}

Callable接口:

public class CallableTest {
    public static void main(String[] args) {
        // 创建 Callable 实例
        Callable<String> task = new Callable<String>() {
            @Override
            public String call() throws Exception {
                String threadName = Thread.currentThread().getName();
                for (int num = 1; num <= 5; num++) {
                    Thread.sleep(1000);
                    System.out.println(threadName + ":" + num);
                }
                return threadName + " 执行完毕";
            }
        };

        // 用 FutureTask 包装 Callable
        FutureTask<String> futureTask1 = new FutureTask<>(task);
        FutureTask<String> futureTask2 = new FutureTask<>(task);

        // 创建线程并启动
        Thread t1 = new Thread(futureTask1, "t1");
        Thread t2 = new Thread(futureTask2, "t2");
        t1.start();
        t2.start();

        try {
            // 获取返回结果(会阻塞直到任务完成)
            String result1 = futureTask1.get();
            String result2 = futureTask2.get();
            System.out.println(result1);
            System.out.println(result2);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

对Callable接口的进一步分析

FutureTask类是什么?

FutureTask是concurrent包中提供的一个类,实现了RunnableFuture接口(RunnableFuture接口继承自Runnable接口和Future接口),内部包装了一个Callable对象,它的run方法会调用Callable.call(),并把返回值或异常保存起来。

换句话说,FutureTask既是任务(实现了Runnable接口),也是结果的容器。(实现了Future接口)

为什么需要用FutureTask?

本质上是因为Thread类的构造方法只接受实现了Runnable接口的任务对象,为了在Thread中执行Callable,我们需要一个适配器,这个适配器就是FutureTask。

执行流程大概如下:

Thread.start() → Thread.run() → FutureTask.run() → Callable.call()

总结

简单来说,Runnable接口和Callable接口的区别在于:Callable接口是一个泛型接口、其中的call方法有返回值,会抛出异常。在创建线程时需要一个实现了Runnable接口的对象,因此在使用Callable接口创建线程时,需要用FutureTask类包装一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值