Java并发——深入理解Executor框架FutureTask类

本文详细介绍了FutureTask的概念及其在Java并发编程中的应用。包括FutureTask的工作原理、如何使用FutureTask实现异步任务处理、FutureTask的状态变化及其实现机制。此外还讨论了FutureTask的常见应用场景。

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

一、什么是Future和FutureTask

一直以来都对FutureTask这个“Future”不理解,为什么叫做“未来的任务呢”?这个“Future”体现在哪里呢?现在终于明白,FutureTask的Future就源自于它的异步工作机制,如果我们在主线程中直接写一个函数来执行任务,这是同步的任务,也就是说必须要等这个函数返回以后我们才能继续做接下的事情,但是如果这个函数返回的结果对接下来的任务并没有意义,那么我们等在这里是很浪费时间的,而FutureTask就提供了这么一个异步的返回结果的机制,当执行一个FutureTask的时候,我们可以接着做别的任务,在将来的某个时间,FutureTask任务完成后会返回FutureTask对象来包装返回的结果,只要调用这个对象的get()方法即可获取返回值。

当然多线程中继承ThreadPoolExecutor和实现Runnable也可以实现异步工作机制,可是他们没有返回值。这时可以使用FutureTask包装Runnable或者Callable对象,再使用FutureTask来执行任务。

Future接口和其唯一的实现类FutureTask类一般用于表示异步计算的结果。Future接口下提供方法来检查计算是否完成,等待其完成,并检索计算结果。 结果只能在计算完成后使用方法get进行检索,如有必要,阻塞,直到准备就绪。 取消由cancel方法执行,isCancelled方法用于检测计算是否被取消,isDone方法用于检测计算是否完成。 提供其他方法来确定任务是否正常完成或被取消。


二、FutureTask的使用

根据FutureTask被执行的进度,FutureTask对象可以处于一下3种状态

未启动:创建了一个FutureTask对象但没有执行futureTask.run();

已启动:futureTask.run()方法被执行的过程中;

已完成:futureTask.run()正常执行结束,或者futureTask被取消(futureTask.cancel()),或者执行futureTask.run()时抛出异常而异常结束;

1、FutureTask的启动:

FutureTask实现了Future接口和Runnable接口,因此FutureTask对象的执行有两种方式:

(1)交给线程池的Execute或submit方法执行

import java.util.concurrent.*;
import static java.util.concurrent.TimeUnit.MILLISECONDS;


class test{
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(5, 10,100, MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
        //用FutureTask包装Runnable或者Callable对象
        FutureTask<String> future = new FutureTask<String>(new Callable<String>() {
            @Override
            public String call() {
                try{
                    String a = "return String";
                    return a;
                }
                catch(Exception e){
                    e.printStackTrace();
                    return "exception";
                }
            }
        });
        //交给线程池的Execute或submit方法执行
        tpe.submit(future);
        try{
            System.out.println(future.get());
        }
        catch(Exception e){
            e.printStackTrace();
        }
        finally{
            tpe.shutdown();
        }
    }
}

(2)由调用线程直接执行:在调用线程中执行futureTask.run()方法

把上述代码的  tpe.submit(future);   替换为   future.run(); 

2、FutureTask执行完后结果的获取  :futureTask.get( )

(1)在已启动的状态调用futureTask.get( )或导致调用线程阻塞,知道FutureTask执行完毕,然后得到返回的FutureTask对象,调用futureTask.get( )获得任务返回值;

(2)在已完成状态调用futureTask.get( ),将导致调用线程立即返回(正常完成,得到FutureTask对象)或者抛出异常(被取消或者因异常而结束);

3、futureTask被取消:futureTask.cancel( )

(1)在未启动状态调用futureTask.cancel( )会导致该任务永远不会再执行;

(2)在已启动状态:

  • 调用futureTask.cancel(true )会以中断的方式尝试停止任务,如果该任务不响应中断则无法停止;
  • 调用futureTask.cancel(false)将不会对正在执行的线程产生影响,也就是已启动的线程会让他执行完毕;

(3)在已完成状态调用futureTask.cancel( )则会返回false;


三、FutureTask的实现

FutureTask是一个基于AQS同步队列实现的一个自定义同步组件,通过对同步状态state的竞争实现acquire或者release操作。

FutureTask的内部类Sync实现了AQS接口,通过对tryAcquire等抽象方法的重写和模板方法的调用来实现内部类Sync的tryAcquireShared等方法,然后聚合Sync的方法来实现FutureTask的get,cancel等方法;

FutureTask的get方法最终会调用AQS.acquireSharedInterruptibly方法,这个方法操作成功的条件是同步状态为RAN或者CANCELLED,也就是说如果这个FutureTask有线程E正在执行,那么这个FutureTask的状态是RUN,因此AQS.acquireSharedInterruptibly方法调用失败,此时调用get方法的线程被阻塞,添加到等待队列中(如下图线程D,其中A,B,C是已经被阻塞添加到等待队列中的线程)。当前面执行FutureTask的线程E执行完毕,那么以原子方式更新同步状态state的值为RAN,并执行AQS.release方法,然后唤醒等待队列中的第一个节点中的线程A,此时线程A出队列获得同步状态,并原子设置state为RUN,当线程A执行完毕,把state原子更新为RUN,然后唤醒线程B,以此类推,因此一颗看出对于一个FutureTask,同一时间只有一个线程执行这个任务。



四、FutureTask使用场景

当一个线程需要等待另一个线程把某个任务执行完以后它才能继续执行时;

有若干线程执行若干任务,每个任务最多只能被执行一次;

当多个线程师徒执行同一个任务,但只能允许一个线程执行此任务,其它线程需要等这个任务被执行完毕以后才能继续执行时;

### Java 中用于多线程操作的工具 #### Executor框架及其组件 Java 提供了`Executor`框架来简化并发程序的设计与开发。其中最核心的部分就是`ExecutorService`接口以及其实现,这些都属于执行者模式的一部分[^2]。 #### 创建线程池的方式 为了更便捷地管理一组工作线程并控制其生命周期,在实际应用中通常会借助于`java.util.concurrent.Executors`工厂所提供的静态方法来获取预配置好的`ExecutorService`实例: - `newFixedThreadPool(int nThreads)`:创建固定大小的工作队列; - `newSingleThreadExecutor()` :创建单一线程执行器; - `newCachedThreadPool()` : 当需要大量短命的任务时适用,它能根据需求动态调整线程数量,并重用已终止的空闲线程[^3]。 ```java // 使用Executors创建不同型线程池的例子 ExecutorService fixedPool = Executors.newFixedThreadPool(10); ExecutorService singlePool = Executors.newSingleThreadExecutor(); ExecutorService cachedPool = Executors.newCachedThreadPool(); ``` 除了上述提到的标准形式外,还有其他一些特殊用途的变种可供选择,比如定时调度专用的ScheduledExecutorService等。 #### 实现Runnable接口创建线程 另一种常见的做法是让某个对象实现`Runnable`接口中的抽象run()函数,之后再把这个实现了特定逻辑的对象传递给新的Thread实例去启动新线程运行之[^4]。 ```java public class Task implements Runnable { @Override public void run(){ // 执行任务的具体业务代码... } } // 启动线程 new Thread(new Task()).start(); ``` 另外还有一种更加灵活的选择——即采用Callable<V>加FutureTask<>组合的形式来进行异步计算,这种方式允许我们在线程结束后获得返回的结果值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值