Java中Runnable和Callable分别什么时候使用

 更新时间:2023年08月07日 15:03:26   作者:古时的风筝  
提到 Java 就不得不说多线程了,就算你不想说,面试官也得让你说呀,那说到线程,就不得不说Runnable和Callable这两个家伙了,二者在什么时候使用呢,下面就来和简单讲讲

提到 Java 就不得不说多线程了,就算你不想说,面试官也得让你说呀,对不对。那说到多线程,就不得提线程了(这不废话吗)。那说到线程,就不得不说RunnableCallable这两个家伙了。

说熟悉也是真熟悉,在刚学习多线程的时候,第一个例子大概就是下面这样子的。

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("执行线程" + Thread.currentThread().getName());
    }
}).start();

看到了 Runnable的身影,有时候还会看到Callable的。

但是说很熟悉吧,印象也不是很大,好像就用了一下这两位的名号,然后剩下的部分就跟他俩没啥关系了。

今天,我们就来看看这两位到底是什么,有什么区别,什时候应该用 Runnable,什么时候又应该用 Callable

Runnable

自从Java诞生,Runnable就存在了,元老中的元老了,在 1.5之前,如果你想使用线程,那必须要实现自 Runnable。因为到了 JDK1.5,JDK 才加入了Callable

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

是不是接口非常简单,就一个抽象方法。

其实还可以再简化一下, @FunctionalInterface标明这个接口是一个函数式接口。函数式接口是在 JDK8才加入的,为的就是实现函数式编程,就那种Lambada表达式。

所以在 JDK1.7中,是没有@FunctionalInterface修饰的,简简单单。我们找到 JDK1.7的 Ruunable实现,是下面这样子

public interface Runnable {
    public abstract void run();
}

想了解更多函数式编程的话,可以看这篇文章:Lambda、函数式接口、Stream 一次性全给你

如果一个线程类要实现 Runnable 接口,则这个类必须定义一个名为 run 的无参数方法。

实现了 Runnable 接口的类可以通过实例化一个 Thread 实例,并将自身作为目标传递来运行。

举个例子

首先定义一个RunnableThread类,并实现自(implements)Runnable接口,然后重写 run方法。

public class RunnableThread implements Runnable{
    @Override
    public void run() {
        System.out.println("当前线程名称"+ Thread.currentThread().getName());
    }
}

使用RunnableThread作为线程类(Thread)实例化的参数,然后调用run方法。

RunnableThread runnableThread = new RunnableThread();
Thread thread = new Thread(runnableThread);
thread.start();

注意,是调用新 new 出来的 Thread 实例的start() 方法,不要调用run方法,虽然我们是重写Runnablerun方法的。调用 run方法并没有创建线程的效果,而是直接在当前线程执行,就和执行一个普通类的普通方法一模一样。

为什么要调用 start()方法呢,我们看看 Threadstart()方法实现中,其实是调用了一个名称为 start0()的 native 方法,native 方法就不是用 Java 实现的了,而是在 JVM 层面的实现。

这个start0方法的主要逻辑就是启动一个操作系统线程,并和 JVM 线程绑定,开辟一些空间来存储线程状态和上下文的数据,然后执行绑定的 JVM 线程(也就是我们实现了Runnable的类)的 run方法的代码块,从而执行我们自定义的逻辑。

还可以用线程池的方式调用

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("thread-pool-%d").build();
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
singleThreadPool.execute(runnableThread);
singleThreadPool.shutdown();

如果 Runnable那么完美的话,就没必要在 JDK1.5中加入和它超级相似的 Callable了。

Runnable有什么不完美的地方吗?就是它的 run方法是没有返回值的。

如果你想在主线程中拿到新开启线程的返回值的话,Runnable就不太方便了。必须要借助共享变量来完成。

所以,如果你的场景是要有返回值的话, 就要 Callable出手了。

Callable

Callable是在 JDK1.5才加入的,为的就是弥补 Runnable没有返回值的缺陷,虽然绝大多数场景都可以用 Runnable来实现。

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Runnable类似的,@FunctionalInterface也是后来加入的,可以不考虑,只是为了函数式写法。

Callable接口只有一个 call方法,并且有一个泛型返回值,可以返回任何类型。

举个例子

首先声明一个类,实现自 Callable接口,返回值为字符串类型

public class CallableThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "线程名称:" + Thread.currentThread().getName();
    }
}

在代码中通过下面的方式调用

CallableThread callableThread = new CallableThread();
FutureTask<String> futureTask = new FutureTask<>(callableThread);
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get();
System.out.println("执行结果= " + result);

看上去就比 Runnable要复杂一点,要借助FutureTask了,因为 Thread类没有接受Callable的构造函数。

使用 FutureTask.get()方法获取执行结果。

在日常的开发中,不建议直接这样用,除非你明确的知道这样做没有问题,否则的话,推荐使用线程池的方式来使用。

CallableThread callableThread = new CallableThread();
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(callableThread);
String result = future.get();
System.out.println("任务执行结果: " + result);
executor.shutdown();

如何选择用哪一个

取舍的基本原则就是需不需要返回值,如果不需要返回值,那直接就选 Runnable,不用犹豫。如果有返回值的话,那更不用犹豫,不要想着借助共享变量的方式。

另外还有一点就是是否需要抛出异常, Runnable是不接受抛出异常的,Callable可以抛出异常。

Runnable适合那种纯异步的处理逻辑。比如每天定时计算报表,将报表存储到数据库或者其他地方,只是要计算,不需要马上展示,展示内容是在其他的方法中单独获取的。

比如那些非核心的功能,当核心流程执行完毕后,非核心功能就自己去执行吧,至于成不成功的,不是特别重要。例如一个购物下单流程,下单、减库存、加到用户的订单列表、扣款是核心功能,之后的发送APP通知、短信通知这些就启动新线程去干去吧。

最后

Runnablejava.lang这个包下,而当JDK1.5发布的时候,新加入的 Callable被安置在了 java.util.concurrent这个包下,这是 Java 里有名的并发编程相关包,各种锁啊、多线程工具类啊,都被放在这个包下。按道理,Runnable 也应该在这里才对。

可见再厉害的项目也是随着项目的扩大而慢慢的规划,而前期的一些看似不太合理的地方,只能做兼容和妥协。

到此这篇关于Java中Runnable和Callable分别什么时候使用的文章就介绍到这了,更多相关Java Runnable Callable内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现树形菜单的方法总结

    Java实现树形菜单的方法总结

    当我们想要展示层级结构,如文件目录、组织结构或分类目录时,树形菜单是一个直观且有效的解决方案,本文为大家整理了java中几种常见方法,希望对大家有所帮助
    2023-08-08
  • 手把手教你实现Java第三方应用登录

    手把手教你实现Java第三方应用登录

    本文主要介绍了手把手教你实现Java第三方应用登录,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • 详细聊一聊Java中的包机制

    详细聊一聊Java中的包机制

    这篇文章主要介绍了Java中包机制的相关资料,Java包机制通过定义、命名和组织类,防止命名冲突,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-05-05
  • IDEA的TODO的使用方式

    IDEA的TODO的使用方式

    这篇文章主要介绍了IDEA的TODO的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • SpringBoot集成SpringSecurity和JWT做登陆鉴权的实现

    SpringBoot集成SpringSecurity和JWT做登陆鉴权的实现

    这篇文章主要介绍了SpringBoot集成SpringSecurity和JWT做登陆鉴权的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-04-04
  • Java面试必备八股文整理

    Java面试必备八股文整理

    这篇文章主要介绍了Java面试必备八股文整理,小伙伴们出去面试的时候会被问到很多java专业性的知识,那么八股文就是为此而出现的,需要的朋友可以参考下
    2023-03-03
  • Java8如何将Array转换为Stream的实现代码

    Java8如何将Array转换为Stream的实现代码

    这篇文章主要介绍了Java8如何将Array转换为Stream的实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • 结合Mybatis聊聊对SQL注入的见解

    结合Mybatis聊聊对SQL注入的见解

    这篇文章主要介绍了结合Mybatis聊聊对SQL注入的见解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • java对象初始化代码详解

    java对象初始化代码详解

    这篇文章主要介绍了java对象初始化代码详解,涉及实例变量的初始化,类变量的初始化等相关介绍几代码示例,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • java 中 zookeeper简单使用

    java 中 zookeeper简单使用

    ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。下面通过本文给大家分享java 中 zookeeper简单使用,需要的朋友参考下吧
    2017-09-09

最新评论