Java 多线程-3 FutureTask学习

一. FutureTask的继承体系

FutureTask 实现了 Runnable 接口和 Future 接口;

FutureTask 也是一个 Runnable 对象;

二. 使用案例

1. 通过Callable构造出FutureTask

通过 callable 对象创建出 FutureTask 对象,实例如下:

java 复制代码
/**
 * 通过 callable 对象创建出 FutureTask 对象
 */
public static void main(String[] args) {

    // 创建一个 Callable 对象
    Callable<Integer> callable = new Callable<>() {
        @Override
        public Integer call() throws Exception {
            TimeUnit.SECONDS.sleep(2);
            int sum = 0;
            for (int i = 0; i < 100; i++) {
                sum = sum + i;
            }
            return sum;
        }
    };

    // 使用 Callable 创建 FutureTask
    FutureTask<Integer> futureTask = new FutureTask<>(callable);

    // 创建一个线程执行 futureTask
    Thread thread = new Thread(futureTask);
    thread.start();

    System.out.println("获取异步任务结果");
    Integer result = null;
    try {
        // 获取异步任务结果,当前主线程会阻塞在这个 get() 中
        result = futureTask.get();
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("result: " + result);
}

打印如下:

获取异步任务结果
result: 4950

2. 通过Runnable构造出FutureTask

通过 runnable 对象创建出 FutureTask 对象,示例如下:

java 复制代码
/**
 * 通过 runnable 对象创建出 FutureTask 对象
 */
public static void main(String[] args) {

    // 创建一个 Runnable 对象
    Runnable runnable = new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            TimeUnit.SECONDS.sleep(2);
        }
    };

    // 使用 Callable 创建 FutureTask
    FutureTask<Integer> futureTask = new FutureTask<>(runnable, null);

    // 创建一个线程执行 futureTask
    Thread thread = new Thread(futureTask);
    thread.start();

    System.out.println("获取异步任务结果");
    Integer result = null;
    try {
        // 获取异步任务结果
        result = futureTask.get();
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("result: " + result);
}

打印如下:

获取异步任务结果
result: null

3. FutureTask.get()设置超时时间

FutureTask.get()设置超时时间,当指定的超时时间到的时候任务还未执行完成,会抛出 TimeoutException;

java 复制代码
/**
 * FutureTask.get() 设置超时时间
 */
public static void main(String[] args) {

    // 创建一个 Callable 对象
    Callable<Integer> callable = new Callable<>() {
        @Override
        public Integer call() throws Exception {
            TimeUnit.SECONDS.sleep(2);
            int sum = 0;
            for (int i = 0; i < 100; i++) {
                sum = sum + i;
            }
            return sum;
        }
    };

    // 使用 Callable 创建 FutureTask
    FutureTask<Integer> futureTask = new FutureTask<>(callable);

    // 创建一个线程执行 futureTask
    Thread thread = new Thread(futureTask);
    thread.start();

    System.out.println("获取异步任务结果");
    Integer result = null;
    try {
        // 获取异步任务结果,超时时间为 1 秒
        // 当 1 秒到的时候任务还未执行完成,会抛出 TimeoutException
        result = futureTask.get(1, TimeUnit.SECONDS);
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("result: " + result);
}

打印如下:

获取异步任务结果
java.util.concurrent.TimeoutException
	at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:204)
	at com.zengqiang.futuretask.Test03.main(Test03.java:48)
result: null

三. 源码分析

1. 属性分析

我们看下 FutureTask 的几个重要属性;

java 复制代码
/**
 * 表示当前 task 状态,注意是当前任务的状态,不是线程的状态!
 * 任务的状态转换情况如下:
 * 	NEW -> COMPLETING -> NORMAL
 * 	NEW -> COMPLETING -> EXCEPTIONAL
 * 	NEW -> CANCELLED
 * 	NEW -> INTERRUPTING -> INTERRUPTED
 */
private volatile int state;


// 当前任务尚未执行
private static final int NEW          = 0;
// 当前任务正在结束,尚未完全结束,一种临界状态
private static final int COMPLETING   = 1;
// 当前任务正常结束
private static final int NORMAL       = 2;
// 当前任务执行过程中发生了异常:内部封装的 callable.run() 抛出了异常
private static final int EXCEPTIONAL  = 3;
// 当前任务被取消
private static final int CANCELLED    = 4;
// 当前任务中断中..
private static final int INTERRUPTING = 5;
// 当前任务已中断
private static final int INTERRUPTED  = 6;


// 可以看到 FutureTask 中的任务是以 Callable 的方式存在的
private Callable<V> callable;


// 正常情况下:任务正常执行结束,outcome 保存执行结果,也就是 callable 的返回值
// 非正常情况:callable 抛出了异常,outcome 保存异常对象
private Object outcome;


// 当前执行该任务的线程
private volatile Thread runner;


// 会有很多线程执行 futureTask.get() 去获取当前任务的结果,如果当前任务还未完成,这些线程会阻塞住
// 这个 waiters 其实就是一个链表,将线程包装成 WaitNode 链表并阻塞挂起
private volatile WaitNode waiters;

简单看下 WaitNode 类,可以看到就是维护了一个线程等待节点链表;

java 复制代码
static final class WaitNode {
    volatile Thread thread;
    volatile WaitNode next;
    WaitNode() { thread = Thread.currentThread(); }
}

2. 两种构造方法

2.1 传参为Callable

传参为 Callable 对象的情况,直接给 FutureTask 的 callable 赋值,当前任务状态为 NEW;

java 复制代码
public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    
    // callable 就是我们自己的任务对象,直接设置给 FutureTask 的 callable 属性
    this.callable = callable;
    
    // 设置当前任务状态为 NEW
    this.state = NEW;
}

2.2 传参为Runnable, result

传参为 Runnable 对象和 result,会将其包装为一个 Callable 对象;

java 复制代码
public FutureTask(Runnable runnable, V result) {
    
    // 将 Runnable 和 result 转换为 callable 对象
    this.callable = Executors.callable(runnable, result);
    
    //设置当前任务状态为 NEW
    this.state = NEW;       
}

可以看到,FutureTask 把 runnable 和 result 包装为了 Callable 对象;

java 复制代码
// --------------------------------- Executors -----------------------------------
public static <T> Callable<T> callable(Runnable task, T result) {
    // 返回的是 RunnableAdater 适配器类,该类实现了 Callable 接口,聚合了 Runnable 对象
    return new RunnableAdapter<T>(task, result);
}



// --------------------------------- RunnableAdapter -----------------------------------
static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    
    // 实际执行的是内部聚合的 Runnable 对象的 run(),并原样返回 result 值
    public T call() {
        task.run();
        return result;
    }
}

3. run()

我们知道 FutureTask 也是一个 Runnable 对象,线程执行 futureTask 的时候,入口是它的 run();

FutureTask 的 run() 内部调用的是封装的 callable 的 call();

java 复制代码
public void run() {
    // 1. 如果当前 state != NEW || 当前任务被其他线程抢占执行,直接返回
    // 如果当前线程抢占当前 futureTask 成功,futureTask 的 runner 线程为当前线程
    if (state != NEW || !RUNNER.compareAndSet(this, null, Thread.currentThread()))
        return;

    // 此时 task 一定是 NEW 状态,并且当前线程抢占 task 成功,runner 为当前线程
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;

            try {
                // 2. 执行 callable.call()
                result = c.call();
                // callable.call() 执行成功,未抛出任何异常,ran 会设置为 true
                ran = true;
            } catch (Throwable ex) {
                // 2.1 callable.call() 抛出了异常,执行 setException(ex)
                result = null;
                ran = false;
                setException(ex);
            }

            if (ran)
                // 2.2 callable.call() 执行成功,执行 set(result)
                set(result);
        }
    } finally {
        runner = null;
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

3.1 set(result)

set(result) 会将任务执行的结果设置给 outcome,并调用 finishCompletion() 把 get() 中阻塞的线程都唤醒;

java 复制代码
protected void set(V v) {
    // 将任务的状态,即 state 改为 COMPLETING
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        // 更新任务的结果 outcome 为 v
        outcome = v;
        
        // 将 result 赋值给 outcome 之后,马上会将当前任务状态修改为 NORMAL 正常结束状态
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL);

        //把 get() 阻塞挂起的线程唤醒
        finishCompletion();
    }
}

3.2 setException(ex)

setException(ex) 会将任务执行的异常设置给 outcome,并调用 finishCompletion() 把 get() 中阻塞的线程都唤醒;

java 复制代码
protected void setException(Throwable t) {
    // 将任务的状态,即 state 改为 COMPLETING
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        // 更新任务的结果 outcome 为异常 t
        outcome = t;
        
        // 将 t 赋值给 outcome 之后,马上会将当前任务状态修改为 EXCEPTIONAL 异常状态
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL);
        
        //把 get() 阻塞挂起的线程唤醒
        finishCompletion();
    }
}

4. get()

如果当前任务还未执行完,当前调用 get() 的线程会被阻塞挂起;

直到任务执行完,唤醒这些阻塞线程,并返回任务执行结果;

java 复制代码
public V get() throws InterruptedException, ExecutionException {
    int s = state;
    
    // 如果当前任务是:未执行、正完成,调用 get() 的外部线程会被阻塞在 get() 上
    if (s <= COMPLETING)
        // awaitDone() 会将外部线程包装为 WaitNode,并阻塞挂起
        // 这些线程被唤醒时,awaitDone() 会返回 task 当前状态,这些线程在里面已经睡了一会了
        s = awaitDone(false, 0L);
	
    // 根据 state 状态,返回对应的结果 outcome
    return report(s);
}

4.1 report(s)

对 state 的状态:

  • normal 状态:正常执行状态,返回 Callable.call() 的结果;
  • cancelled 状态:被取消状态,抛出 CancellationException 异常;
  • exceptional 状态:异常状态,抛出 ExecutionException 异常;
java 复制代码
private V report(int s) throws ExecutionException {
    
    // 正常情况下:outcome 保存的是 callable 运行结束的结果
    // 异常情况下:outcome 保存的是 callable 的 call() 产生的异常
    Object x = outcome;
    
    if (s == NORMAL)
        // 1. 正常执行状态,返回 Callable.call() 的结果
        return (V)x;

    if (s >= CANCELLED)
        // 2. 被取消状态,抛出 CancellationException 异常
        throw new CancellationException();

    // 3. 异常状态,抛出 ExecutionException 异常
    // ExecutionException 中的原始异常为 Callable.call() 中抛出的异常
    throw new ExecutionException((Throwable)x);
}

4.2 awaitDone(false, 0)

将当前线程包装为 WaitNode,作为新的链表头,并挂起阻塞当前线程;

  • 将当前线程包装为 WaitNode 入链表作为新链表头,并且将当前节点对应的线程挂起,LockSupport.park();线程进入阻塞状态;
  • 线程什么时候会被唤醒呢?当任务被执行完成时,runner 线程会唤醒所有 WaitNode 中等待的线程;
java 复制代码
private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;

    // 进入自旋
    for (;;) {
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }

        // 1. 如果当前线程是被其它线程 unpark(thread) 唤醒的话
        // 获取当前任务最新状态,如果状态 state > COMPLETING,直接返回该 state
        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        
        // 2. 任务状态 state == COMPLETING,表示有其他线程即将执行完任务,当前线程等待一小段时间
        else if (s == COMPLETING)
            Thread.yield();
        
        // 3. 为当前线程创建 WaitNode
        else if (q == null)
            q = new WaitNode();
        else if (!queued){
            // 将当前线程的 WaitNode 入链表中,并且作为链表的头,queued 置为 true
            q.next = waiters;
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset, waiters, q);
        }
        
        else if (timed) {
            // 4.1 含等待时间的挂起,执行 LockSupport.parkNanos(this, nanos) 阻塞一段时间
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            // 4.2 不含等待时间的挂起,执行 LockSupport.park(this)
            LockSupport.park(this);
    }
}

5. get(long timeout, TimeUnit unit)

含阻塞时间的 get(),当指定的超时时间到的时候任务还未执行完成,会抛出 TimeoutException;

java 复制代码
public V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException {

    int s = state;
    
    // 如果当前任务是:未执行、正完成,调用 get() 的外部线程会被阻塞在 get() 上
    if (s <= COMPLETING &&
        // awaitDone() 会将外部线程包装为 WaitNode,并阻塞挂起
        // 这些线程被唤醒时,awaitDone() 会返回 task 当前状态,这些线程在里面已经睡了一会了
        // 指定的超时时间到的时候任务还未执行完成的话,抛出 TimeoutException
        (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
        throw new TimeoutException();
    return report(s);
}
相关推荐
Daniel 大东12 分钟前
idea 解决缓存损坏问题
java·缓存·intellij-idea
wind瑞18 分钟前
IntelliJ IDEA插件开发-代码补全插件入门开发
java·ide·intellij-idea
HappyAcmen19 分钟前
IDEA部署AI代写插件
java·人工智能·intellij-idea
马剑威(威哥爱编程)24 分钟前
读写锁分离设计模式详解
java·设计模式·java-ee
鸽鸽程序猿25 分钟前
【算法】【优选算法】前缀和(上)
java·算法·前缀和
修道-032326 分钟前
【JAVA】二、设计模式之策略模式
java·设计模式·策略模式
九圣残炎31 分钟前
【从零开始的LeetCode-算法】2559. 统计范围内的元音字符串数
java·算法·leetcode
当归102444 分钟前
若依项目-结构解读
java
hlsd#1 小时前
关于 SpringBoot 时间处理的总结
java·spring boot·后端
iiiiiankor1 小时前
C/C++内存管理 | new的机制 | 重载自己的operator new
java·c语言·c++