[Juc进阶]Callable、Future和FutureTask

news2024/11/26 10:25:55

一、Callable 与 Runnable

先说一下java.lang.Runnable吧,它是一个接口,在它里面只声明了一个run()方法:

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

由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。

Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call():

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

可以看到,这是一个泛型接口,该接口声明了一个名称为call()的方法,同时这个方法可以有返回值V,也可以抛出异常。call()方法返回的类型就是传递进来的V类型。

那么怎么使用Callable呢?一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本:

<T> Future<T> submit(Callable<T> task);    //submit提交一个实现Callable接口的任务,并且返回封装了异步计算结果的Future。
<T> Future<T> submit(Runnable task, T result);  //submit提交一个实现Runnable接口的任务,并且指定了在调用Future的get方法时返回的result对象。
Future<?> submit(Runnable task);  //submit提交一个实现Runnable接口的任务,并且返回封装了异步计算结果的Future。

因此我们只要创建好我们的线程对象(实现Callable接口或者Runnable接口),然后通过上面3个方法提交给线程池去执行即可。

明显能看到区别:
1.Callable能接受一个泛型,然后在call方法中返回一个这个类型的值。而Runnable的run方法没有返回值
2.Callable的call方法可以抛出异常,而Runnable的run方法不会抛出异常。

补充:
实现Runnable接口和实现Callable接口的区别:
1、Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的。
2、Callable规定的方法是call(),Runnable规定的方法是run()。
3、Callable的任务执行后可返回值,而Runnable的任务是不能返回值(是void)。
4、call方法可以抛出异常,run方法不可以。
5、运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
6、加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。

二、Future

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
    Future接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。我们看看Future接口的源码:

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

在Future接口中声明了5个方法,下面依次解释每个方法的作用:
cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
isDone方法表示任务是否已经完成,若任务完成,则返回true;
**get()**方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
**get(long timeout, TimeUnit unit)**用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

也就是说Future提供了三种功能:
1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。

例子: submit(Callable task)

public class Main {
  public static void main(String[] args) throws InterruptedException, ExecutionException {
  ExecutorService executor = Executors.newFixedThreadPool(2);
  //创建一个Callable,3秒后返回String类型
  Callable myCallable = new Callable() {
    @Override
    public String call() throws Exception {
      Thread.sleep(3000);
      System.out.println("calld方法执行了");
      return "call方法返回值";
    }
  };
  System.out.println("提交任务之前 "+getStringDate());
  Future future = executor.submit(myCallable);
  System.out.println("提交任务之后,获取结果之前 "+getStringDate());
  System.out.println("获取返回值: "+future.get());
  System.out.println("获取到结果之后 "+getStringDate());
  }
  public static String getStringDate() {
    Date currentTime = new Date();
    SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
    String dateString = formatter.format(currentTime);
    return dateString;
    }
  }

通过executor.submit提交一个Callable,返回一个Future,然后通过这个Future的get方法取得返回值。
看一下输出:

提交任务之前 12:13:01
提交任务之后,获取结果之前 12:13:01
calld方法执行了
获取返回值: call方法返回值
获取到结果之后 12:13:04

例子:submit(Runnable task)
因为Runnable是没有返回值的,所以如果submit一个Runnable的话,get得到的为null:

Runnable myRunnable = new Runnable() {
  @Override
  public void run() {
  try {
    Thread.sleep(2000);
    System.out.println(Thread.currentThread().getName() + " run time: " + System.currentTimeMillis());
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  }
};

Future future = executor.submit(myRunnable);
System.out.println("获取的返回值: "+future.get());

输出为:

pool-1-thread-1 run time: 1493966762524
获取的返回值: null

三、FutureTask

我们先来看一下FutureTask的实现:

public class FutureTask<V> implements RunnableFuture<V>

FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

分析:
FutureTask除了实现了Future接口外还实现了Runnable接口,因此FutureTask也可以直接提交给Executor执行。 当然也可以调用线程直接执行(FutureTask.run())。

接下来我们根据FutureTask.run()的执行时机来分析其所处的3种状态:
(1)未启动,FutureTask.run()方法还没有被执行之前,FutureTask处于未启动状态,当创建一个FutureTask,而且没有执行FutureTask.run()方法前,这个FutureTask也处于未启动状态。
(2)已启动,FutureTask.run()被执行的过程中,FutureTask处于已启动状态。
(3)已完成,FutureTask.run()方法执行完正常结束,或者被取消或者抛出异常而结束,FutureTask都处于完成状态。

下面我们再来看看FutureTask的方法执行示意图(方法和Future接口基本是一样的,这里就不过多描述了)

分析:
(1)当FutureTask处于未启动或已启动状态时,如果此时我们执行FutureTask.get()方法将导致调用线程阻塞;当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或者抛出异常。
(2)当FutureTask处于未启动状态时,执行FutureTask.cancel()方法将导致此任务永远不会执行。当FutureTask处于已启动状态时,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果任务取消成功,cancel(…)返回true;但如果执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时cancel(…)返回false。当任务已经完成,执行cancel(…)方法将返回false。

最后我们给出FutureTask的两种构造函数:

public FutureTask(Callable<V> callable) {
}
public FutureTask(Runnable runnable, V result) {
}

事实上,FutureTask是Future接口的一个唯一实现类。

示例:

package com.demo.test;

import java.util.concurrent.Callable;

public class Task implements Callable<Integer>{
    
    @Override
    public Integer call() throws Exception {
        System.out.println("子线程在进行计算");
        Thread.sleep(3000);
        int sum = 0;
        for(int i=0;i<100;i++)
            sum += i;
        return sum;
    }
}
package com.demo.test;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class CallableTest1 {
    
    public static void main(String[] args) {
        //第一种方式
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        executor.submit(futureTask);
        executor.shutdown();
         
        //第二种方式,注意这种方式和第一种方式效果是类似的,只不过一个使用的是ExecutorService,一个使用的是Thread
//        Task task = new Task();
//        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
//        Thread thread = new Thread(futureTask);
//        thread.start();
        
        
         //第三种,使用Callable+Future获取执行结果
 //        ExecutorService executor = Executors.newCachedThreadPool();
        //创建Callable对象任务  
 //       Task task = new Task();
        //提交任务并获取执行结果  
//        Future<Integer> result = executor.submit(task);
        //关闭线程池  
//        executor.shutdown();         
       
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
         
        System.out.println("主线程在执行任务");
         
        try {
            if(futureTask.get()!=null){  
                System.out.println("task运行结果"+futureTask.get());
            }else{
                System.out.println("future.get()未获取到结果"); 
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
         
        System.out.println("所有任务执行完毕");
    }
}

运行结果:

子线程在进行计算
主线程在执行任务
task运行结果4950
所有任务执行完毕

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/765530.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多个文件保存位置不同:如何一键批量重命名为相同名称

在日常工作中&#xff0c;我们会经常遇到需要修改文件名称&#xff0c;文件改名方法也是很多种呢&#xff0c;可以手动修改或使用工具批量重命名&#xff0c;一般大家修改文件或文件夹重命名&#xff0c;都是在同一个保存位置&#xff0c;有没有遇到多个文件保存位置不同&#…

抖音账号矩阵系统源码.搭建技术开发分享

技术自研框架开发背景&#xff1a; 抖音账号矩阵系统是一种基于数据分析和管理的全新平台&#xff0c;能够帮助用户更好地管理、扩展和营销抖音账号。 部分源码分享&#xff1a; //计算分页$active_list_all $Video_model->getCount($where);$page_libs new Libs_Pagin…

Android平台如何高效率实现GB28181对接?

技术背景 GB28181协议是一种用于设备状态信息报送的协议&#xff0c;可以在不同设备之间进行通信和数据传输。 在安卓系统上实现GB/T 28181非常必要&#xff0c;GB28181协议实现分两部分&#xff0c;一部分是信令&#xff0c;另外一部分就是媒体数据的编码。 信令主要包括S…

CenterNet Objects as Points 论文学习

论文链接&#xff1a;Objects as Points 1. 解决了什么问题&#xff1f; 目标检测的任务是从图像中检出目标的矩形框。现有的检测方法大多会穷举所有潜在的目标位置&#xff0c;然后做分类。这非常浪费资源、低效率&#xff0c;并且依赖后处理。单阶段方法会在图像上放置大量…

049、事务设计之分布式基本原理

隔离级别 iso定义的隔离级别 可串行化 可重复读 读已提交 读未提交 隔离级别区分的现象 脏读&#xff1a; 一个事务读取另一个未提交的事务所做更改 不可重复度 &#xff1a;同一事务中&#xff0c;前后执行相同的语句&#xff0c;出来的记录不一样 幻读&#xff1a; 同一事务…

ImVoxelNet 论文学习

论文链接&#xff1a;ImVoxelNet: Image to Voxels Projection for Monocular and Multi-View General-Purpose 3D Object Detection 1. 解决了什么问题&#xff1f; RGB 图像成本低、数据源丰富&#xff0c;可以提供场景和物体的视觉信息&#xff0c;但不包括场景几何结构的…

guitar pro2023最新专业的吉他制谱、扒谱软件

guitar pro8是一款打谱编曲软件。我们是会在guitar pro8里面去得到更多自由的打谱设置操作。软件中的改变都很强大好用&#xff0c;是会等等更轻松的编曲制作和设置等等。这里面的所有使用都是很精彩的&#xff0c;是能够去随意的使用更多不同的音符节拍的设置。想要自己进行打…

全志F1C200S嵌入式驱动开发(spi-nand驱动)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 和v3s一样,f1c200s也支持tf卡、spi-nor、spi-nand启动。前面也说过,tf卡由于机械结构的原因,更适合拿来学习,spi-nor和spi-nand比较适合用来进行工业部署和消费娱乐领域。只是s…

使用3DS Max 创建未来派螺栓枪模型

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 步骤 1 创建一个框并将其转换为可编辑多边形&#xff08;右键单击>转换为&#xff1a;>转换为可编辑多边形&#xff09;&#xff0c;然后使用连接添加一系列边循环&#xff0c;如下图所示。 步骤 2 …

vue往window里存数据

1、存 window._getAction () > {return actions}2、取 const parentAction window._getAction()

使用openvpn docker及frp docker工具构建虚拟专业网络(V-P-N)

使用openvpn docker及frp docker工具构建虚拟专业网络(V-P-N) 借助Docker和OpenVPN技术&#xff0c;您可以在短时间内设置并运行VPN服务器&#xff0c;并保证您的服务器安全。 运行环境 Ubuntu 16.04 TLS Docker version 19.03.8, build afacb8b7f0 OpenVPN Android Client …

Mysql教程(三):Mysql图形化工具DataGrip的基本使用

Mysql教程&#xff08;三&#xff09;&#xff1a;Mysql图形化工具DataGrip的基本使用 引言&#xff1a;为什么选择DataGrip 智能查询控制台 可以让您在不同的模式中执行查询&#xff0c;并提供本地历史记录&#xff0c;从而记录您的所有活动&#xff0c;避免工作丢失。高效的…

matplotlib 3D

import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D import numpy as np# 创建一个三维坐标轴 fig plt.figure() ax fig.add_subplot(221, projection3d) xx fig.add_subplot(222) yy fig.add_subplot(223) xy fig.add_subplot(224)# 生成示例数据…

(vite/webpack 通用)vue 白屏 Uncaught Syntaxerror: Unexpected token ‘?‘

如果可以实现记得点赞分享&#xff0c;谢谢老铁&#xff5e; 一、问题的描述 突然收到业务方发来的反馈&#xff1a; 浏览器一片空白&#xff0c;开控制台输出&#xff1a;Uncaught Syntaxerror: Unexpected token ? 这个是语法错误。 二、 如果是用vite来构建&#xff1a…

react项目打包部署到服务器上+宝塔面板的操作

首先进行配置react项目&#xff0c;在webpack.json中 加上homepage:.或‘./’ 首先需要项目打包 npm run build 我们会得到一个build 运行build中的index文件如果是空白的话我们需要更改上面的操作重新打包 .或./ 打包完我们需要进入腾讯云或阿里云的安全组中进行配置端口 …

数字图像学笔记 —— 19.肤色检测

文章目录 什么是肤色检测需要使用OpenCV函数颜色空间转换颜色范围位运算 什么是肤色检测 在没有AI之前&#xff0c;肤色检测是计算机视觉的一项常见任务。其根本思路是划定一个颜色区域&#xff0c;只要属于该颜色区域的像素就认为属于肤色。但是这种方法的精确度不高&#xf…

nodejs调取exe执行文件连接websocket

最近有个需求&#xff0c;就是需要前端调取一个封装成exe文件的websoket服务。 很显然&#xff0c;在浏览器的框架下&#xff0c;根本不可能完成这个事情。以为浏览器的沙盒机制和安全策略&#xff0c;是不允许读取本地文件的。 所以&#xff0c;我们需要nodejs的帮助。 那么…

企业应如何做好软件供应链安全管理?

随着软件供应链攻击日益普遍&#xff0c;Gartner 将其列为2022 年的第二大威胁。Gartner 预测&#xff0c;到 2025 年&#xff0c;全球 45% 的组织将遭受一次或多次软件供应链攻击&#xff0c;是2021年的3倍。这些攻击一旦成功&#xff0c;将给企业带来毁灭性打击&#xff0c;因…

mmdet3d预处理(下)| train pipeline

mmdet3d预处理&#xff08;下&#xff09;—— train pipeline 文章目录 mmdet3d预处理&#xff08;下&#xff09;—— train pipeline基类 BaseTransformLoadPointsFromFileLoadAnnotations3D标签信息&#xff1a;源码 ObjectSample源码 ObjectNoise输入参数源码RandomFlip3D…

【FFNN负荷预测】基于人工神经网络的空压机负荷预测(Matlab代码实现)

&#x1f4cb;&#x1f4cb;&#x1f4cb;本文目录如下&#xff1a;&#x1f381;&#x1f381;&#x1f381; 目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 2.1 算例1 2.2 算例2 2.3 算例3 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码、数据、文章 &#x1f4a…