【Java面试经典】说说Runnable与Callable的区别

326 阅读3分钟
  1. 定义和返回值的区别

    • Runnable 接口

      • 定义在java.lang包中,是一个函数式接口,只包含一个抽象方法run。这个方法没有返回值,签名为public void run()。它主要用于定义一个任务单元,这个任务单元在被线程执行时,只是单纯地执行一段代码逻辑,不需要返回结果给调用者。例如,一个简单的打印任务可以这样定义:
     class PrintTask implements Runnable {
         @Override
         public void run() {
             System.out.println("执行打印任务");
         }
     }
  • Callable 接口

    • 定义在java.util.concurrent包中,同样是函数式接口,包含一个抽象方法callcall方法有返回值,并且可以抛出异常,其签名为V call() throws ExceptionV是返回值类型,由实现类确定)。这意味着使用Callable定义的任务可以在执行完成后返回一个结果给调用者,适用于那些需要获取异步任务执行结果的场景。例如,一个计算任务可以这样定义:
     import java.util.concurrent.Callable;
     class CalculateTask implements Callable<Integer> {
         @Override
         public Integer call() {
             return 2 + 3;
         }
     }
  1. 用法区别

    • Runnable 的用法

      • 创建线程执行任务:要执行Runnable任务,通常需要将Runnable实现类的实例传递给Thread类的构造函数,然后调用Threadstart方法来启动线程。例如:
     class MyRunnable implements Runnable {
         @Override
         public void run() {
             System.out.println("这是一个使用Runnable的任务");
         }
     }
     public class Main {
         public static void main(String[] args) {
             MyRunnable myRunnable = new MyRunnable();
             Thread thread = new Thread(myRunnable);
             thread.start();
         }
     }
  • 在线程池中的应用:在Executor框架的线程池中,Runnable任务可以通过ExecutorServiceexecute方法提交。例如:
     import java.util.concurrent.ExecutorService;
     import java.util.concurrent.Executors;
     class AnotherRunnable implements Runnable {
         @Override
         public void run() {
             System.out.println("在线程池中执行的Runnable任务");
         }
     }
     public class Main {
         public static void main(String[] args) {
             ExecutorService executorService = Executors.newFixedThreadPool(3);
             AnotherRunnable anotherRunnable = new AnotherRunnable();
             executorService.execute(anotherRunnable);
             executorService.shutdown();
         }
     }
  • Callable 的用法

    • 结合 Future 获取结果:因为Callable有返回值,所以在使用时通常需要和FutureFutureTask一起配合。Future用于表示异步任务的结果,它提供了方法来检查任务是否完成、获取任务结果等。FutureTaskFuture的一个实现,它同时实现了Runnable接口,所以可以将Callable实例包装到FutureTask中,再交给Thread启动或者提交给线程池。例如:
     import java.util.concurrent.Callable;
     import java.util.concurrent.ExecutionException;
     import java.util.concurrent.FutureTask;
     class MyCallable implements Callable<String> {
         @Override
         public String call() {
             return "这是Callable返回的结果";
         }
     }
     public class Main {
         public static void main(String[] args) {
             MyCallable myCallable = new MyCallable();
             FutureTask<String> futureTask = new FutureTask<>(myCallable);
             Thread thread = new Thread(futureTask);
             thread.start();
             try {
                 System.out.println(futureTask.get());
             } catch (InterruptedException | ExecutionException e) {
                 e.printStackTrace();
             }
         }
     }
  • 在线程池中的应用(与 Runnable 类似但有区别) :在Executor框架的线程池中,Callable任务通过ExecutorServicesubmit方法提交,这个方法会返回一个Future对象用于获取结果。例如:
     import java.util.concurrent.Callable;
     import java.util.concurrent.ExecutorService;
     import java.util.concurrent.Executors;
     import java.util.concurrent.Future;
     class CalculateCallable implements Callable<Integer> {
         @Override
         public Integer call() {
             return 5 * 6;
         }
     }
     public class Main {
         public static void main(String[] args) {
             ExecutorService executorService = Executors.newFixedThreadPool(3);
             CalculateCallable calculateCallable = new CalculateCallable();
             Future<Integer> future = executorService.submit(calculateCallable);
             try {
                 System.out.println("计算结果为:" + future.get());
             } catch (InterruptedException | ExecutionException e) {
                 e.printStackTrace();
             }
             executorService.shutdown();
         }
     }
  1. 适用场景的区别

    • Runnable 适用场景

      • 简单的任务执行:当只需要执行一段简单的代码逻辑,不需要返回结果给调用者时,如打印日志、发送简单的通知等任务,使用Runnable非常合适。
      • 资源消耗型任务的异步执行:对于一些资源消耗型任务,如文件读取、网络请求等,将它们放在单独的线程中以Runnable任务的形式执行,可以避免阻塞主线程,提高程序的响应速度。
    • Callable 适用场景

      • 需要获取任务结果的计算任务:在分布式计算、数据处理等场景中,当一个任务需要返回计算结果,例如对大数据集进行复杂的数学计算、从数据库中获取并处理数据后返回结果等,Callable接口是更好的选择。
      • 异步任务链:在一些复杂的异步任务处理中,一个任务的结果可能是下一个任务的输入,使用Callable可以方便地获取每个任务的结果,构建异步任务链。例如,先从数据库获取用户信息,然后根据用户信息进行复杂的业务逻辑计算,最后返回最终结果,就可以通过多个Callable任务和Future来实现。