Java多线程中的Callable和Future详解
作者:weixin_34274029
前言
创建线程的两种方式,一种是直接继承Thread,另外一种就是实现Runnable接口。
这两种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。
如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
一、Runnable接口
先看一下java.lang.Runnable吧,它是一个接口,在它里面只声明了一个run()方法:
public interface Runnable { public abstract void run(); }
由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。
二、Callable接口
Callable接口位于java.util.concurrent包下,在它里面也只声明了一个方法,只不过这个方法叫做call()。
public interface Callable<V> { V call() throws Exception; }
可以看到,这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。
Callable接口可以看作是Runnable接口的补充,call方法带有返回值,并且可以抛出异常。
三、FutureTask类
如何获取Callable的返回结果呢?一般是通过FutureTask这个中间媒介来实现的。
整体的流程是这样的:把Callable实例当作参数,生成一个FutureTask的对象,然后把这个对象当作一个Runnable,作为参数另起线程。
3.1 FutureTask的结构
3.2 FutureTask的启动
由于FutureTask实现了Runnable,因此它既可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行。
下面以Thread包装线程方式启动来说明一下。
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Demo { public static void main(String[] args) throws Exception { Callable<Integer> call = new Callable<Integer>() { public Integer call() throws Exception { System.out.println("计算线程正在计算结果..."); Thread.sleep(3000); return 1; } }; FutureTask<Integer> task = new FutureTask<>(call); Thread thread = new Thread(task); thread.start(); System.out.println("main线程干点别的..."); Integer result = task.get(); System.out.println("从计算线程拿到的结果为:" + result); } }
四、Future接口
FutureTask继承体系中的核心接口是Future。
Future的核心思想是:一个方法,计算过程可能非常耗时,等待方法返回,显然不明智。可以在调用方法的时候,立马返回一个Future,可以通过Future这个数据结构去控制方法f的计算过程。
这里的控制包括:
- get方法:获取计算结果(如果还没计算完,也是必须等待的)
- cancel方法:还没计算完,可以取消计算过程
- isDone方法:判断是否计算完
- isCancelled方法:判断计算是否被取消
补充:同样是获取线程的计算结果,Java则显得很繁琐,而C语言的实现则简单的多。
看下面的例子
假设有两个函数:
void * dose_do(void * a) { for (int i = 0; i < 5; i++) { sleep(1); puts("does_do"); } return NULL; } void * dose_not(void * a) { for (int i = 0; i < 5; i++) { sleep(1); puts("does_not"); } return NULL; }
这两个函数都返回了void指针,因为void指针可以指向存储器中任何数据类型的数据,线程函数的返回类必须是void *。
1、创建线程
创建线程可以使用多种线程库,在此我们使用最流行的一种:POSIX线程库,也叫pthread。
必须包含#include <pthread.h>头文件。
我们使用pthread_create() 函数创建并运行一个线程,而且每个线程都需要把线程信息保存在一个pthread_t类型的数据中。
// 线程对象 pthread_t t0; pthread_t t1; if (pthread_create(&t0, NULL, dose_not, NULL) == -1) { error("无法创建线程t0"); } if (pthread_create(&t1, NULL, dose_do, NULL) == -1) { error("无法创建线程t1"); }
2、获取线程返回值
上边的两个函数将会独立的在线程中运行直到结束,但是我们需要知道这两个函数什么时候结束。可以使用pthread_join()函数等待函数结束,它会接受线程函数的返回值,并保存在一个void *类型的数据中。那么这个函数是如何得知线程结束的呢?当得到线程函数的返回值的时候,就表明线程函数结束了。这也是为什么线程函数必须要有返回值的原因。
void *result; if (pthread_join(t0, &result) == -1) { error("无法回收线程t0"); } if (pthread_join(t1, &result) == -1) { error("无法回收线程t1"); }
我们来看全部代码:
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> // 错误处理函数 void error(char *msg) { fprintf(stderr, "Error: %s %s", msg, strerror(errno)); exit(1); } void * dose_not(void * a) { for (int i = 0; i < 5; i++) { sleep(1); puts("does_not"); } return NULL; } void * dose_do(void * a) { for (int i = 0; i < 5; i++) { sleep(1); puts("does_do"); } return NULL; } int main(int argc, const char * argv[]) { // 线程对象 pthread_t t0; pthread_t t1; if (pthread_create(&t0, NULL, dose_not, NULL) == -1) { error("无法创建线程t0"); } if (pthread_create(&t1, NULL, dose_do, NULL) == -1) { error("无法创建线程t1"); } void *result; if (pthread_join(t0, &result) == -1) { error("无法回收线程t0"); } if (pthread_join(t1, &result) == -1) { error("无法回收线程t1"); } return 0; }
到此这篇关于Java多线程中的Callable和Future详解的文章就介绍到这了,更多相关Java多线程Callable和Future内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!