Java多线程中的Callable和Future详解

 更新时间:2023年08月26日 09:59:07   作者:weixin_34274029  
这篇文章主要介绍了Java多线程中的Callable和Future详解,创建线程的两种方式,一种是直接继承Thread,另外一种就是实现Runnable接口,本文提供了部分代码,需要的朋友可以参考下

前言

创建线程的两种方式,一种是直接继承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的结构

lingpaitong.png

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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java easyexcel导出报内存溢出的问题解决

    Java easyexcel导出报内存溢出的问题解决

    在Java开发时,使用EasyExcel处理大数据量导出可能遇到内存溢出问题,本文深入分析了内存溢出的原因,并提出了优化策略,感兴趣的可以了解一下
    2024-10-10
  • SSH框架网上商城项目第20战之在线支付平台

    SSH框架网上商城项目第20战之在线支付平台

    这篇文章主要为大家详细介绍了SSH框架网上商城项目第20战之在线支付平台,关于第三方支付的内容从本文开始,感兴趣的小伙伴们可以参考一下
    2016-06-06
  • Java 数据结构哈希算法之哈希桶方式解决哈希冲突

    Java 数据结构哈希算法之哈希桶方式解决哈希冲突

    实际上哈希桶是解决哈希表冲突的一种方法。常见的解决冲突的两种方法:分离链接法、开放定址法。其中使用分离链接法,得到的对应关系即为哈希桶
    2022-02-02
  • Mybatis-Plus分页的使用与注意事项

    Mybatis-Plus分页的使用与注意事项

    分页查询每个人程序猿几乎都使用过,下面这篇文章主要给大家介绍了关于Mybatis-Plus分页的使用与注意事项的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-04-04
  • Intellij IDEA中一次性折叠所有Java代码的快捷键设置

    Intellij IDEA中一次性折叠所有Java代码的快捷键设置

    这篇文章主要介绍了Intellij IDEA中一次性折叠所有Java代码的快捷键设置,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • java Quartz定时器任务与Spring task定时的几种实现方法

    java Quartz定时器任务与Spring task定时的几种实现方法

    本篇文章主要介绍了java Quartz定时器任务与Spring task定时的几种实现方法的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-02-02
  • Java中不可或缺的关键字volatile详析

    Java中不可或缺的关键字volatile详析

    volatile是Java提供的一种轻量级的同步机制,下面这篇文章主要给大家介绍了关于Java中不可或缺的关键字volatile的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • 深入理解Java中的final关键字_动力节点Java学院整理

    深入理解Java中的final关键字_动力节点Java学院整理

    Java中的final关键字非常重要,它可以应用于类、方法以及变量。这篇文章中我将带你看看什么是final关键字以及使用final的好处,具体内容详情通过本文学习吧
    2017-04-04
  • 深入理解Java中的WeakHashMap

    深入理解Java中的WeakHashMap

    这篇文章主要介绍了深入理解Java中的WeakHashMap,WeakHashMap从名字可以得知主要和Map有关,不过还有一个Weak,我们就更能自然而然的想到这里面还牵扯到一种弱引用结构,因此想要彻底搞懂,我们还需要知道四种引用,需要的朋友可以参考下
    2023-09-09
  • Java详解数据类型的定义与使用

    Java详解数据类型的定义与使用

    Java 是一种类型安全语言,编译器存储在变量中的数值具有适当的数据类型。学习任何一种编程语言都要了解其数据类型,本文将详细介绍 Java 中的数据类型
    2022-04-04

最新评论