深入理解Java中的FutureTask:用法和原理

简介: 【10月更文挑战第28天】`FutureTask` 是 Java 中 `java.util.concurrent` 包下的一个类,实现了 `RunnableFuture` 接口,支持异步计算和结果获取。它可以作为 `Runnable` 被线程执行,同时通过 `Future` 接口获取计算结果。`FutureTask` 可以基于 `Callable` 或 `Runnable` 创建,常用于多线程环境中执行耗时任务,避免阻塞主线程。任务结果可通过 `get` 方法获取,支持阻塞和非阻塞方式。内部使用 AQS 实现同步机制,确保线程安全。

一、FutureTask 概述


1. 定义


  • FutureTask是 Java 中的一个类,位于java.util.concurrent包中,它实现了RunnableFuture接口,而RunnableFuture接口又同时继承了RunnableFuture接口。这意味着FutureTask既可以作为一个Runnable被线程执行,又可以作为一个Future来获取异步计算的结果。


2. 作用


  • 异步计算:在多线程编程中,FutureTask用于封装一个可调用任务(例如实现了Callable接口的任务),并允许在一个单独的线程中执行该任务。这样可以在执行耗时操作(如网络请求、文件读取、复杂计算等)时,不会阻塞主线程或其他线程的执行。
  • 结果获取:提供了一种机制来获取异步计算的结果。通过FutureTaskget方法,可以在任务完成后获取其执行结果,如果任务尚未完成,get方法可以阻塞当前线程,直到任务完成并返回结果。


二、FutureTask 用法


1. 创建 FutureTask


  • 基于 Callable 接口
  • 首先,需要创建一个实现Callable接口的类。Callable接口与Runnable接口类似,但它可以返回一个结果并且可以抛出异常。例如:


import java.util.concurrent.Callable;
// 定义一个Callable任务,用于计算两个数的和
class AddTask implements Callable<Integer> {
    private int num1;
    private int num2;
    public AddTask(int num1, int num2) {
        this.num1 = num1;
        this.num2 = num2;
    }
    @Override
    public Integer call() throws Exception {
        return num1 + num2;
    }
}


  • 然后,使用这个Callable任务创建一个FutureTask对象:


import java.util.concurrent.FutureTask;
// 创建FutureTask对象
FutureTask<Integer> futureTask = new FutureTask<>(new AddTask(3, 5));


  • 基于 Runnable 接口和结果生成器(不常用)
  • 也可以基于Runnable接口创建FutureTask,但需要额外提供一个结果生成器(Callable)来定义任务的结果。这种方式相对复杂,不常用。例如:


import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicInteger;
// 定义一个Runnable任务,用于简单的计数
class CounterRunnable implements Runnable {
    private AtomicInteger count = new AtomicInteger(0);
    @Override
    public void run() {
        count.incrementAndGet();
    }
}
// 定义一个Callable任务,用于获取计数结果
class CounterResultCallable implements Callable<Integer> {
    private CounterRunnable counterRunnable;
    public CounterResultCallable(CounterRunnable counterRunnable) {
        this.counterRunnable = counterRunnable;
    }
    @Override
    public Integer call() throws Exception {
        return counterRunnable.count.get();
    }
}
// 创建基于Runnable和结果生成器的FutureTask
CounterRunnable counterRunnable = new CounterRunnable();
FutureTask<Integer> futureTaskFromRunnable = new FutureTask<>(counterRunnable, new CounterResultCallable(counterRunnable));


2. 执行 FutureTask


  • 提交给线程池执行
  • 通常会将FutureTask提交给线程池来执行,这样可以更好地管理线程资源。例如,使用ExecutorService线程池:


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 提交FutureTask到线程池执行
executorService.submit(futureTask);
// 关闭线程池(注意:这里的关闭方式不会立即终止正在执行的任务)
executorService.shutdown();


  • 直接在单独线程中执行
  • 也可以直接在一个单独的线程中执行FutureTask,不过这种方式不太灵活,且不利于线程资源的管理:


import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadPoolExecutor;
// 创建一个FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(new AddTask(3, 5));
// 创建一个线程并执行FutureTask
Thread thread = new Thread(futureTask);
thread.start();


3. 获取结果


  • 阻塞式获取结果
  • 使用FutureTaskget方法可以获取任务的结果。如果任务还未完成,调用get方法的线程会被阻塞,直到任务完成并返回结果。例如:


import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
try {
    // 获取FutureTask的结果,可能会阻塞
    Integer result = futureTask.get();
    System.out.println("结果是: " + result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}


  • 非阻塞式检查结果
  • 通过FutureTaskisDone方法,可以在不阻塞的情况下检查任务是否已经完成。例如:


if (futureTask.isDone()) {
    try {
        Integer result = futureTask.get();
        System.out.println("结果是: " + result);
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
} else {
    System.out.println("任务尚未完成");
}


三、FutureTask 原理


1. 内部状态


  • 状态变量FutureTask内部使用一个volatile修饰的整数变量来表示状态,这个变量有不同的取值,对应不同的任务状态,如新建(NEW)、已完成(COMPLETED)、已取消(CANCELLED)等。这些状态的转换是原子操作,通过Unsafe类或CAS(Compare - and - Swap)机制来保证线程安全。
  • 状态转换:例如,当任务开始执行时,状态从NEW转换为RUNNING,当任务执行成功完成后,状态转换为COMPLETED,如果任务被取消,状态转换为CANCELLEDINTERRUPTED(取决于取消的方式)。


2. 实现机制


  • 基于 AQS(AbstractQueuedSynchronizer)的同步机制FutureTask的底层实现依赖于AQS来实现同步和阻塞。AQS是一个用于构建锁和同步器的框架,它提供了基于队列的等待和唤醒机制。FutureTask通过继承AQS来实现自己的同步逻辑。
  • 等待获取结果:当一个线程调用FutureTaskget方法时,如果任务尚未完成,该线程会被封装成一个Node添加到AQS的等待队列中,然后线程会被阻塞。这个等待队列是一个双向链表结构,用于管理等待获取结果的线程。
  • 任务完成后的唤醒:当任务完成后,FutureTask会通过AQS的唤醒机制,将等待队列中的线程逐个唤醒。唤醒的线程会再次尝试获取任务的结果,如果任务已经完成,就可以成功获取结果,否则会再次被阻塞。
  • 结果存储和可见性:任务的结果存储在FutureTask内部的一个变量中,通过volatile修饰来保证结果的内存可见性。当任务完成后,结果会被正确地写入这个变量,并且其他等待获取结果的线程可以立即看到这个结果。


3. 与线程池的协作


  • 线程池中的任务调度:当FutureTask被提交给线程池(如ExecutorService)时,线程池会从自己的工作队列中取出FutureTask并分配给一个空闲的线程来执行。线程池中的线程在执行FutureTask时,与直接执行FutureTask的原理是一样的,都是通过AQS来实现同步和阻塞,以及通过状态转换来管理任务的执行过程。
  • 线程池的资源管理和优化:线程池可以根据自身的配置和当前的负载情况,合理地分配资源来执行FutureTask。例如,一个ThreadPoolExecutor可以根据核心线程数、最大线程数、队列容量等参数来决定是立即执行FutureTask,还是将其放入队列中等待执行,或者拒绝执行(如果队列已满且线程数达到最大线程数)。这有助于提高系统的整体性能和资源利用率。
相关文章
|
4月前
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
148 0
|
4月前
|
存储 缓存 安全
深入讲解 Java 并发编程核心原理与应用案例
本教程全面讲解Java并发编程,涵盖并发基础、线程安全、同步机制、并发工具类、线程池及实际应用案例,助你掌握多线程开发核心技术,提升程序性能与响应能力。
216 0
|
4月前
|
人工智能 安全 Java
Go与Java泛型原理简介
本文介绍了Go与Java泛型的实现原理。Go通过单态化为不同类型生成函数副本,提升运行效率;而Java则采用类型擦除,将泛型转为Object类型处理,保持兼容性但牺牲部分类型安全。两种机制各有优劣,适用于不同场景。
165 24
|
5月前
|
存储 缓存 Java
我们来详细讲一讲 Java NIO 底层原理
我是小假 期待与你的下一次相遇 ~
205 2
|
5月前
|
XML JSON Java
Java 反射:从原理到实战的全面解析与应用指南
本文深度解析Java反射机制,从原理到实战应用全覆盖。首先讲解反射的概念与核心原理,包括类加载过程和`Class`对象的作用;接着详细分析反射的核心API用法,如`Class`、`Constructor`、`Method`和`Field`的操作方法;最后通过动态代理和注解驱动配置解析等实战场景,帮助读者掌握反射技术的实际应用。内容翔实,适合希望深入理解Java反射机制的开发者。
548 13
|
5月前
|
算法 Java 索引
说一说 Java 并发队列原理剖析
我是小假 期待与你的下一次相遇 ~
|
Java Android开发
【Java 虚拟机原理】Java 引用类型 ( 强引用 | 软引用 | 弱引用 | 虚引用 | 静态变量 )
【Java 虚拟机原理】Java 引用类型 ( 强引用 | 软引用 | 弱引用 | 虚引用 | 静态变量 )
225 0
|
1月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
143 1
|
1月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
161 1