详解Java中Callable和Future的区别
作者:iamswf 发布时间:2023-07-25 21:18:58
Java中为什么需要Callable
在java中有两种创建线程的方法:
一种是继承Thread
类,重写run
方法:
public class TestMain {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
}
}
class MyThread extends Thread {
public void run() {
System.out.println("MyThread running...");
}
}
第二种是使用Runnable
创建一个线程:
public class TestMain {
public static void main(String[] args) {
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Thread created with runnable running...");
}
};
Thread t1 = new Thread(r1);
t1.start();
}
}
其实这两种方式,底层都是执行Thread
类的run
方法:
无论使用这里的哪种方式创建线程,都无法在线程结束时return一个返回值。但是在非常多的场景下,我们都需要在线程执行结束时,将执行的结果封装为一个返回值返回给主线程(或者调用者线程)。因此java在1.5版本时,在java.util.concurrent
包引入了Callable
接口,用于线程执行完时return一个返回值。
Callable和Runnable的区别
Runnable和Callable都是接口,分别定义如下:
package java.lang;
/**
* The <code>Runnable</code> interface should be implemented by any
* class whose instances are intended to be executed by a thread. The
* class must define a method of no arguments called <code>run</code>.
* <p>
* @since JDK1.0
*/
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
package java.util.concurrent;
/**
* A task that returns a result and may throw an exception.
* Implementors define a single method with no arguments called
* {@code call}.
*
* <p>The {@code Callable} interface is similar to {@link
* java.lang.Runnable}, in that both are designed for classes whose
* instances are potentially executed by another thread. A
* {@code Runnable}, however, does not return a result and cannot
* throw a checked exception.
*
* <p>The {@link Executors} class contains utility methods to
* convert from other common forms to {@code Callable} classes.
*
* @see Executor
* @since 1.5
* @author Doug Lea
* @param <V> the result type of method {@code call}
*/
@FunctionalInterface
public interface Callable<V> {
/**
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
可以看出,Callable
和Runnable
主要有两点区别:
有返回值;
可以抛出异常(这里抛出的异常,会在future.get()时可以通过
ExectionException
捕获);
因此可以看出,Callable
更加实用。这里举个Callable
使用的例子:
Callable callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int i = new Random().nextInt(5);
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return i;
}
};
虽然Callable
接口的call
方法可以返回执行结果,但是有两个问题需要解决:
线程的创建只能通过
Runnable
,通过Callable
又如何创建线程?如何获取执行结果?
答案是Future
和RunnableFuture
。
Future和RunnableFuture
Future
是一个接口,看下定义:
package java.util.concurrent;
/**
* A {@code Future} represents the result of an asynchronous
* computation. Methods are provided to check if the computation is
* complete, to wait for its completion, and to retrieve the result of
* the computation. The result can only be retrieved using method
* {@code get} when the computation has completed, blocking if
* necessary until it is ready. Cancellation is performed by the
* {@code cancel} method. Additional methods are provided to
* determine if the task completed normally or was cancelled. Once a
* computation has completed, the computation cannot be cancelled.
* If you would like to use a {@code Future} for the sake
* of cancellability but not provide a usable result, you can
* declare types of the form {@code Future<?>} and
* return {@code null} as a result of the underlying task.
*
* @see FutureTask
* @see Executor
* @since 1.5
* @author Doug Lea
* @param <V> The result type returned by this Future's {@code get} 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 {@code cancel} is called,
* this task should never run. If the task has already started,
* then the {@code mayInterruptIfRunning} parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.
*
* <p>After this method returns, subsequent calls to {@link #isDone} will
* always return {@code true}. Subsequent calls to {@link #isCancelled}
* will always return {@code true} if this method returned {@code true}.
*
* @param mayInterruptIfRunning {@code true} if the thread executing this
* task should be interrupted; otherwise, in-progress tasks are allowed
* to complete
* @return {@code false} if the task could not be cancelled,
* typically because it has already completed normally;
* {@code true} otherwise
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* Returns {@code true} if this task was cancelled before it completed
* normally.
*
* @return {@code true} if this task was cancelled before it completed
*/
boolean isCancelled();
/**
* Returns {@code true} if this task completed.
*
* Completion may be due to normal termination, an exception, or
* cancellation -- in all of these cases, this method will return
* {@code true}.
*
* @return {@code true} if this task completed
*/
boolean isDone();
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
*/
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
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
* @throws TimeoutException if the wait timed out
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
可以看出,Future
可以用来表示线程的未来执行结果:一个容器,这个容器内将来存放的是线程的执行结果,线程执行完之前该容器内没有值,但是线程一旦执行成功(Callable
的call
方法返回之后),就会将结果存入该容器。从Future
的接口定义可看出,Future
不仅支持阻塞获取执行结果,还支持取消任务的执行,判断任务是否执行完成等。因此通过Future
,主线程(或者调用者线程)可以跟进子现场的执行情况。
Callable
其实和Runnable
很像,都会执行一个任务,只不过Callable
可以返回执行的结果。一般将执行结果封装到Future
,调用者线程即可以通过Future
获取Callable
的执行结果了。因此,一般Callable
会和Future
搭配使用。
但是问题来了:java创建线程,需要Runnable
,获取执行结果又需要Future
。因此RunnableFuture
来了:
可以看出,通过RunnableFuture
,既可以创建线程,又可以获取线程的执行结果,当然RunnableFuture
也是一个接口,我们一般情况下会使用它的具体实现类FutureTask
。
那可能又有人要问了,Callable
又是如何建立联系的呢?看下FutureTask
的使用方式就明白了:
public class TestMain {
public static void main(String[] args) {
Callable callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int i = new Random().nextInt(5);
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return i;
}
};
/**
* callable创建futureTask
* FutureTask实现了RunnableFuture接口,因此即是Runnable又是Future
* 作为Runnable可以传入Thread创建线程并执行
* 作为Future,可以用来获取执行的结果。
* 这里创建出来的futureTask对象有人称为"具柄"或者"存根",大家可以理解为用来获取线程执行结果的一个"引用"即可。
*/
FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
// 作为Runnable使用
Thread thread = new Thread(futureTask);
thread.start();
try {
// 作为Future使用
Integer integer = futureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
因此FutureTask
是Callable
到Runnable
的桥梁。
不使用Callable和Future,仅使用Runnable实现相同功能
下面我们看下,如果不使用Callable
和Future
,仅使用Runnable
如何实现返回值。
public class TestMain {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start();
Object o = myRunnable.get();
System.out.println(o);
}
}
class MyRunnable implements Runnable {
// 存储执行结果
private Object outCome = null;
@Override
public void run() {
int i = new Random().nextInt(5);
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 存储执行结果
outCome = i;
// 产出结果后唤醒等待的get方法
synchronized (this) {
notifyAll();
}
}
public synchronized Object get() {
while(outCome == null) {
try {
// 等待产出结果
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return outCome;
}
}
可以看出,通过Runnable
实现更加麻烦,因此这也体现出了Callable
+Future
的优势。
来源:https://juejin.cn/post/7168111807515754527


猜你喜欢
- 本文实例讲述了C#中WinForm跨线程访问控件的实现方法,分享给大家供大家参考。具体实现方法如下:1、跨线程访问控件委托和类的定义usin
- 本文实例为大家分享了Android实现二级列表购物车功能的具体代码,供大家参考,具体内容如下MainActivity:package com
- SQLite是Android自带的关系型数据库,是一个基于文件的轻量级数据库。Android提供了3种操作数据的方式,SharedPrefe
- 本文介绍了spring cloud Feign使用中遇到的问题总结,分享给大家,具体如下:问题一:在前面的示例中,我们讲过@RequestM
- 无论是用Eclipse还是用Android Studio做android开发,都会接触到jar包,全称应该是:Java Archive,即j
- 前言各位精通CRUD的老司机,相信大家在工作中mybatis或者mybatisplus使用的肯定是比较多的,那么大家或多或少都应该对下面的行
- 前言springboot提供了 spirng-boot-starter-test 以供开发者使用单元测试,在引入 spring-boot-s
- JUnit是Java中最有名的单元测试框架,用于编写和运行可重复的测试,多数Java的开发环境都已经集成了JUnit作为单元测试的工具。好的
- 什么是自动填充有些表中会有更新时间、创建时间、更新人或者创建人这些字段。每次对数据进行新增、删除、修改时都需要对这些字段进行设置。传统的做法
- 前言这篇文章主要介绍Spring Boot的统一功能处理模块,也是AOP的实战环节。1.用户登录权限效验在学习Spring AOP之前,用户
- spinner组件有点类型于HTML中的下拉框<Select></select>的样子,让用户每次从下拉框中选取一个
- Spring Boot如何实现分布式系统中的服务发现和注册?随着互联网的快速发展,越来越多的企业开始将自己的业务迁移到分布式系统中。在这种情
- 在Value中的Style.xml中,添加: <style name="NoTitle"
- IO操作字节流java.io.InputStream 输入流,主要是用来读取文件内容的。java.io.OutputStream 输出流,主
- MyBatis在注解上使用动态SQL1、用script标签包围然后像xml语法一样书写@Select({"<script&g
- 1. 引言 * (Interceptor)实现对每一个请求处理前后进行相关的业务处理,类似于Servlet的Filter。我们可以让普通的B
- 1、认识XML解析技术1.1、XML相关概念(1)DTD:XML语法规则,是XML文件的验证机制,可以通过比较XML文档和DTD文件看文档是
- 本文实例讲述了C#实现的文件操作封装类。分享给大家供大家参考,具体如下:最近发现群共享里面有个C# 文件操作封装类,其方法是调用Window
- 快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。步骤为:1.从数
- 背景在使用Spring Boot Mvc的项目中,使用Long类型作为id的类型,但是当前端使用Number类型接收Long类型数据时,由于