线程池系列 之 Future<V> 接口

1继承关系图

在这里插入图片描述

2 Runnable接口

Runnable接口是线程辅助类,仅定义了一个方法run()方法。灵活:Runnable可以继承其他类 实现对Runnable实现类的增强,避免了Thread类由于继承Thread类而无法继承其他类的问题。共享资源:Runnable接口的run()方法可以被多个线程共享,适用于多个进程处理一种资源的问题。

Runnable或Thread的区别:
在这里插入图片描述
(1)Runnable的实现方式是实现其接口即可
(2)Thread的实现方式是继承其类
(3)Runnable接口支持多继承
(4)Thread实现了Runnable接口并进行了扩展,而Thread和Runnable的实质是实现的关系,不是同类东西,所以Runnable或Thread本身没有可比性。

@FunctionalInterface
public interface Runnable {
    // 运行
    public abstract void run();
}

3Future<V> 接口

Future接口是Java线程Future模式的实现,可以来进行异步计算。Future模式可以这样来描述:我有一个任务,提交给了Future,Future替我完成这个任务,期间我自己可以去做任何想做的事情。一段时间之后,我就可以从Future那儿取出结果。

Future接口提供方法来检测任务是否被执行完、等待任务执行完获得结果、也可以设置任务执行的超时时间,这个设置超时的方法就是实现Java程序执行超时的关键。

Future接口是一个泛型接口,严格的格式应该是Future<V>,其中V代表了Future执行的任务返回值的类型。

public interface Future<V> {
 
    // 取消异步任务的执行:如果异步任务已经完成或者已经被取消,或者由于某些原因不能取消,则会返回false;
    // 如果任务还没有被执行,则会返回true并且异步任务不会被执行;
    // 如果任务已经开始执行了但是还没有执行完成:若mayInterruptIfRunning为true,则会立即中断执行任务的线程并返回true,若mayInterruptIfRunning为false,则会返回true且不会中断任务执行线程。
    boolean cancel(boolean mayInterruptIfRunning);
 
    // 判断任务是否被取消:如果任务在结束(正常执行结束或者执行异常结束)前被取消则返回true,否则返回false。
    boolean isCancelled();
 
    // 判断任务是否已经完成,如果完成则返回true,否则返回false。需要注意的是:任务执行过程中发生异常、任务被取消也属于任务已完成,也会返回true。
    boolean isDone();
 
    // 获取任务执行结果,如果任务还没完成则会阻塞等待直到任务执行完成。
    // 如果任务被取消则会抛出CancellationException异常,如果任务执行过程发生异常则会抛出ExecutionException异常,如果阻塞等待过程中被中断则会抛出InterruptedException异常。
    V get() throws InterruptedException, ExecutionException;
    
    // 带超时时间的get()版本,如果阻塞等待过程中超时则会抛出TimeoutException异常。
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

4Callable<V> 接口

Callable接口和Runnable接口功能相似,执行Callable对象中的call方法可以抛出Checked Exception。可以获得任务执行返回值;通过与Future的结合,可以实现利用Future来跟踪异步计算的结果。

Callable和Runnable有以下几点不同:
(1) Callable规定的方法是call(),而Runnable规定的方法是run()
(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的
(3) call()方法可抛出异常,而run()方法是不能抛出异常的
(4) 运行Callable任务可拿到一个Future对象,获取线程的执行结果

@FunctionalInterface
public interface Callable<V> {
    // 计算结果,如果无法执行,则引发异常
    V call() throws Exception;
}

我们需要在主线程中开启多个线程去执行一个任务,然后收集各个线程的返回结果并将最终结果进行汇总,这是就需要用到 Callable 接口。

package com.zs.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class ThreadDemo implements Callable<String> {
    private String name;
    public ThreadDemo(String name) {
        this.name = name;
    }
    @Override
    public String call() {
        return name;
    }
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);

        // 创建接收结果的列表集合
        List<Future> list = new ArrayList<Future>();

        for(int i = 0 ; i < 5; i++) {
            //创建线程对象
            Callable c = new ThreadDemo("callThread-" + i);
            //将线程对象提交到线程池中,并将返回结果接收
            // <T> Future<T> submit(Callable<T> task);
            Future future = pool.submit(c);
            System.out.println("线程" + i + "已经加入线程池");
            //将返回结果加入集合
            list.add(future);
        }
        //打印返回结果
        for (Future future : list) {
            try {
                System.out.println(future.get().toString());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        //关闭线程池
        pool.shutdown();
    }
}

5FutureTask<V>

在这里插入图片描述
FutureTask是一个提供异步计算的结果的任务。FutureTask可以用来包装Callable或者Runnbale对象。因为FutureTask实现了Runnable接口,所以FutureTask也可以被提交给Executor

除了实现了Future接口以外,FutureTask还实现了Runnable接口,因此FutureTask交由Executor执行,也可以直接用线程调用执行(futureTask.run())。根据FutureTask的run方法执行的时机,FutureTask可以处于以下三种执行状态:

1、未启动:在FutureTask.run()还没执行之前,FutureTask处于未启动状态。当创建一个FutureTask对象,并且run()方法未执行之前,FutureTask处于未启动状态。
2、已启动:FutureTask对象的run方法启动并执行的过程中,FutureTask处于已启动状态。
3、已完成:FutureTask正常执行结束,或者FutureTask执行被取消(FutureTask对象cancel方法),或者FutureTask对象run方法执行抛出异常而导致中断而结束,FutureTask都处于已完成状态。
在这里插入图片描述

  • 当FutureTask处于未启动或者已启动的状态时,调用FutureTask对象的get方法会将导致调用线程阻塞。当FutureTask处于已完成的状态时,调用FutureTask的get方法会立即放回调用结果或者抛出异常。
  • 当FutureTask处于未启动状态时,调用FutureTask对象的cancel方法将导致线程永远不会被执行;
  • 当FutureTask处于已启动状态时,调用FutureTask对象cancel(true)方法将以中断执行此任务的线程的方式来试图停止此任务;
  • 当FutureTask处于已启动状态时,调用FutureTask对象cancel(false)方法将不会对正在进行的任务产生任何影响;
  • 当FutureTask处于已完成状态时,调用FutureTask对象cancel方法将返回false;
  • 可以把FutureTask交给Executor执行;也可以通ExecutorService.submit(…)方法返回一个FutureTask,然后执行FutureTask.get()方法或FutureTask.cancel(…)方法。除此以外,还可以单独使用FutureTask。

当一个线程需要等待另一个线程把某个任务执行完后它才能继续执行,此时可以使用FutureTask。 假设有多个线程执行若干任务,每个任务最多只能被执行一次。当多个线程试图同时执行同一个任务时,只允许一个线程执行任务,其他线程需要等待这个任务执行完后才能继续执行。

get 方法的实现关键在 awaitDone(boolean timed, long nanos) 方法,主要的数据结构就是 Waiter 节点,加上 waiters(栈顶),栈中存放的是 等待获取结果的线程;以此实现阻塞功能!!!注意是:后进先出

1成员变量

public class FutureTask<V> implements RunnableFuture<V> {
 
    /**
     * 当前任务的运行状态。
     *
     * 可能存在的状态转换
     * NEW -> COMPLETING -> NORMAL(有正常结果)
     * NEW -> COMPLETING -> EXCEPTIONAL(结果为异常)
     * NEW -> CANCELL
     * ED(无结果)
     * NEW -> INTERRUPTING -> INTERRUPTED(无结果)
     */
 
    private volatile int state;
    private static final int NEW          = 0;   //表示是个新的任务或者还没被执行完的任务,这是初始状态。
    
    private static final int COMPLETING   = 1;   //任务已经执行完成或者执行任务的时候发生异常,但是任务执行结果或者异常原因还没有保存到outcome字段
    // (outcome字段用来保存任务执行结果,如果发生异常,则用来保存异常原因)
    // 的时候,状态会从NEW变更到COMPLETING。但是这个状态会时间会比较短,属于中间状态。
    
    private static final int NORMAL       = 2;   // 任务已经执行完成并且任务执行结果已经保存到outcome字段,状态会从COMPLETING转换到NORMAL,这是一个最终态。
    
    private static final int EXCEPTIONAL  = 3;   // 任务执行发生异常并且异常原因已经保存到outcome字段中后,状态会从COMPLETING转换到EXCEPTIONAL,这是一个最终态。
 
    private static final int CANCELLED    = 4;  // 任务还没开始执行或者已经开始执行但是还没有执行完成的时候,用户调用了cancel(false)方法取消任务且不中断任务执行线程,这个时候状态会从NEW转化为CANCELLED状态,这是一个最终态。
    
    private static final int INTERRUPTING = 5;   // 任务还没开始执行或者已经执行但是还没有执行完成的时候,用户调用了cancel(true)方法取消任务并且要中断任务执行线程但是还没有中断任务执行线程之前,状态会从NEW转化为INTERRUPTING,这是一个中间状态。
    private static final int INTERRUPTED  = 6;   //调用interrupt()中断任务执行线程之后状态会从INTERRUPTING转换到INTERRUPTED,这是一个最终态。
 
 所有值大于COMPLETING的状态都表示任务已经执行完成(任务正常执行完成,任务执行异常或者任务被取消;
 
    //将要执行的任务
    private Callable<V> callable;
 
    //用于get()返回的结果,也可能是用于get()方法抛出的异常
    private Object outcome; // non-volatile, protected by state reads/writes
 
    //执行callable的线程,调用FutureTask.run()方法通过CAS设置
    private volatile Thread runner;
 
    //栈结构的等待队列,该节点是栈中的最顶层节点(等待获取结果的线程栈!!!)
    private volatile WaitNode waiters;
 
    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long stateOffset;
    private static final long runnerOffset;
    private static final long waitersOffset;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = FutureTask.class;
            stateOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("state"));
            runnerOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("runner"));
            waitersOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("waiters"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}
static final class WaitNode {
 
    // 注意:下面两个变量都是volatile修饰的
 
    // 等待节点对应的线程
    volatile Thread thread;
    // 后继节点
    volatile WaitNode next;
 
    WaitNode() { 
        thread = Thread.currentThread(); 
    }
}

2 构造方法


public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

public FutureTask(Runnable runnable, V result) {
 
    // 通过Executors.callable()将runnable和result合成一个callable
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

3 awaitDone(boolean timed, long nanos)

awaitDone是futureTask实现阻塞的关键方法

1、计算deadline,也就是到某个时间点后如果还没有返回结果,那么就超时了。

2、进入自旋,也就是死循环。

3、首先判断是否响应线程中断。对于线程中断的响应往往会放在线程进入阻塞之前。

4、判断state值,如果>COMPLETING表明任务已经取消或者已经执行完毕,就可以直接返回了。

5、如果任务还在执行,则为当前线程初始化一个等待节点WaitNode,入等待队列。这里和AQS的等待队列类似,只不过:

6Node只关联线程,而没有状态。AQS里面的等待节点是有状态的。

7、计算nanos,判断是否已经超时。如果已经超时,则移除所有等待节点,直接返回state。超时的话,state的值仍然还是COMPLETING8、如果还未超时,就通过LockSupprot类提供的方法在指定时间内挂起当前线程,等待任务线程唤醒或者超时唤醒。
  

出口在:

1)线程被中断,抛出异常

(2)任务可能已经完成或者被取消了,将等待节点的thread置空,return状态s

(3)超时,返回状态

当用户实现Callable()接口定义好任务之后,把任务交给其他线程进行执行。FutureTask内部维护一个任务状态,任何操作都是围绕着这个状态进行,并随时更新任务状态。任务发起者调用get()获取执行结果的时候,如果任务还没有执行完毕,则会把自己放入阻塞队列中然后进行阻塞等待。当任务执行完成之后,任务执行线程会依次唤醒阻塞等待的线程。调用cancel()取消任务的时候也只是简单的修改任务状态,如果需要中断任务执行线程的话则调用Thread.interrupt()中断任务执行线程。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值