Callable与Future

本文详细介绍了Java中Callable和Future的概念与使用,包括它们与Runnable的区别、FutureTask的内部状态及其转换、get和cancel方法的行为。文章指出,Callable适用于需要线程返回结果和抛出异常的情况,而Future则用于保存异步任务的结果,允许添加回调并在任务完成时执行。FutureTask结合了Runnable和Future的功能,可以方便地与Executor一起使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Runnable封装了一个异步运行的任务,只有一个没有参数和返回值的run方法,同时也不能抛出异常;Callable与其类似,只有一个call方法,但是它有参数和返回值,还能抛出异常,Callable可以看作是Runnable的加强版。

Runnable和Callable的定义如下:

在这里插入图片描述

在这里插入图片描述

从中可以看到:
1、 Callable能接受一个泛型,然后在call方法中返回一个这个类型的值,例如Callable<Integer>表示一个最终返回Integer对象的异步计算。而Runnable的run方法没有返回值。
2、 Callable的call方法可以抛出异常,而Runnable的run方法不会抛出异常。

适合用Callable的情况

1、 需要线程返回结果

当一个线程需要另外一个比较耗时的线程的运算结果的时候,如果我们用Runnable实现线程的话,就需要使用共享变量和线程间的通信来完成,同时还需要等待线程执行完毕,可见就相对复杂且效率低。

2、 需要线程抛出异常

我们在使用Runnable执行一些可能会抛出异常的任务的时候,都只能在run方法内部进行异常的捕获,而不能抛给上一级调用者进行异常的处理,这也造成了极大的不便。

由于Runnable的局限性,在以上两种情况的时候,使用Callable都拥有较高的效率。

Future

Callable线程允许返回值,由于线程是异步执行的,所以我们就需要一个数据结构来保存异步任务的结果,这个数据结构就是Future。

Future表示一个可能还没有完成的异步任务的结果,针对这个结果,可以添加Callback以便在任务执行成功或失败后作出相应的操作。

Future接口提供了五个方法:

在这里插入图片描述

它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。

Future的核心思想是:一个线程的执行任务可能非常耗时,等待该线程返回,显然不明智。所以可以在启动线程的时候就返回一个Future,可以通过Future去控制任务的执行。

Future的继承和实现关系如下:

在这里插入图片描述

其中最常用的就是FutureTask。

FutureTask

FutureTask实现了RunnableFuture接口,而RunnableFuture同时继承了Future和Runnable。

在这里插入图片描述
在这里插入图片描述

所以,FutureTask除了实现Future接口外,还实现了Runnable接口。因此,FutureTask可以交给Executor执行,也可以由调用线程直接执行,并且可以直接返回其运算结果。它既是一个Runnable又是一个Future。

FutureTask的状态

根据FutureTask中run方法被执行的时机,FutureTask处于不同的状态,在FutureTask中定义了如下几种状态:

在这里插入图片描述

一个FutureTask被new出来,即处于NEW状态,COMPLETING用的是进行时态,表示正在执行,NORMAL代表顺利完成,EXCEPTIONAL表示执行过程中出现异常,CANCELLED代表执行过程被取消,INTERRUPTING表示正在被中断,即已经接受到了中断的指令,在完成中断前的最后工作,而INTERRUPTED表示已经被中断了。

一个FutureTask可能的状态迁移过程:

1、 NEW -> COMPLETING -> NORMAL:正常结束
2、 NEW -> COMPLETING -> EXCEPTIONAL:异常结束
3、 NEW -> CANCELLED:取消而结束
4、 NEW -> INTERRUPTING -> INTERRUPTED:中断而结束

FutureTask的get和cancel的执行

当FutureTask处于未启动或已启动状态时,执行FutureTask.get()方法将导致调用线程阻塞;当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或抛出异常。

当FutureTask处于未启动状态时,执行FutureTask.cancel()方法将导致此任务永远不会被执行;
当FutureTask处于已启动状态时,执行FutureTask.cancel(true)方法将以中断执行此任务线程的方式来试图停止任务;
当FutureTask处于已启动状态时,执行FutureTask.cancel(false)方法将不会对正在执行此任务的线程产生影响(让正在执行的任务运行完成);
当FutureTask处于已完成状态时,执行FutureTask.cancel()将返回false。

如下图:

在这里插入图片描述

Callable+FutureTask使用

FutureTask包装机制与Callable搭配使用非常便利,它将可以使Callable转换成Runnable和Future,由new Thread(FutureTask).start()启动,并可调用FutureTask.get()获取计算结果。

例如:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest {

	public static void main(String[] args) {
		//1 创建一个Callable线程
		Callable<Integer> myCallable = new Callable<Integer>() {
			
			@Override
			public Integer call() throws Exception {
				// TODO Auto-generated method stub
				int num = 0;
				for (int i = 1;i <= 100;i++ ) {
					num = num + i;
				}
				return num;
			}
		};
		
		//2 FutureTask包装Callable
		FutureTask<Integer> myTask = new FutureTask<>(myCallable);
		//3 启动线程
		new Thread(myTask).start();
		//4 获取结果
		try {
			Integer result = myTask.get();
			System.out.println(result);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

如果FutureTask被中断了,get()方法将会抛出InterruptedException,如果FutureTask出现了异常,则get()方法会抛出ExecutionException异常。

FutureTask中run方法

FutureTask实现了Runnable接口,所以它也实现了run方法,当通过Thread启动的时候,将会执行run方法中的逻辑。

其run方法的源码如下:
在这里插入图片描述

首先是判断当前FutureTask的状态,如果当前状态并不支持线程启动则直接return返回,即当FutureTask的状态state不为NEW,或者runner不为空(即已经有线程在执行);如果runner为空,则将runner的值更新为当前线程。

如果当前状态允许执行,则创建一个Callable对象来接收被包装的Callable对象,如果该Callable对象不为空且处于NEW状态,则创建result变量来保存直接调用call方法计算的结果,布尔类型的变量ran表示是否成功执行。

如果成功调用call方法,则将result设置为FutureTask的放回结果。如果call方法抛出异常则result置为null,ran置为false。最后finally语句块中将负责启动的Thread置为null,并判断是否被中断。

注:compareAndSwap方法是Java并发包中的一种CAS操作,java.util.concurrent中大量使用了CAS操作,涉及到并发的地方都调用了sun.misc.Unsafe类方法进行CAS操作。这个方法有四个参数,其中第一个参数为需要改变的对象,第二个为偏移量(即之前求出来的valueOffset的值),第三个参数为期待的值,第四个为更新后的值。整个方法的作用即为若调用该方法时,value的值与expect这个值相等,那么则将value修改为update这个值,并返回一个true,如果调用该方法时,value的值与expect这个值不相等,那么不做任何操作,并范围一个false。

在这里插入图片描述

FutureTask包装Runnable

其实,FutureTask不仅可以包装Callable,还可以包装Runnable,只不过是先将Runnable通过Excutors包装成Callable。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值