【Java基础知识】Future的实现

一 线程池的提交方式

在向线程池提交线程的时候,有两个方法,
一个是executor(Runnable runnable)方法,
另一个是submit(Runnable runnable)方法,

  Future<Integer> result = executor.submit(task); //有返回值
  executor.executor(task)  //没有返回值

二 Runnable、Callable、FutureTask的使用

(1) Runnable是一个任务的概念,这个任务没有返回值
(2) Callable也是一个任务,这个任务有返回值, 不能单独使用,必须配合FutureTask一起使用
(3) FutureTask是一个任务,FutureTask继承了Runnable、Callable, 通过FutureTask可以获取到任务执行的状态
(4) FutureTask任务执行完成完成后,将结构通过Future接口返回,调用者可以调用Future#get()方法获取到数据

public class Test {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        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 {
            System.out.println("task运行结果"+result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } 
        System.out.println("所有任务执行完毕");
    }
}
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;
    }
}

三 FutureTask的实现

3.1) 字段属性

(1) 任务的状态:
在这里插入图片描述
(2) 具体执行的任务和返回值、执行的线程、等待结果的线程队列
在这里插入图片描述

3.2) 构造方法

构造方法的主要作用是将Runnable、Callable转换为FutureTask
在这里插入图片描述

3.3 ) 任务的执行

任务执行在这里插入图片描述
FutureTask#run() 方法

public void run() {
   // 如果状态不是 NEW,说明任务已经执行过或者已经被取消,直接返回
   // 如果状态是 NEW,则尝试把执行线程保存在 runnerOffset(runner字段),如果赋值失败,则直接返回
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
       // 获取构造函数传入的 Callable 值
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
               // 正常调用 Callable 的 call 方法就可以获取到返回值
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
               // 保存 call 方法抛出的异常
                setException(ex);
            }
            if (ran)
               // 保存 call 方法的执行结果
                set(result);
        }
    } finally {        
        runner = null;       
        int s = state;
       // 如果任务被中断,则执行中断处理
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

FutureTask中保存了Callable,在run()方法中调用Callable.call()方法就可以获取到返回值,然后通过 set(result) 保存正常程序运行结果,或通过 setException(ex) 保存程序异常信息
FutureTask#设置返回值方法

** The result to return or exception to throw from get() */
private Object outcome; // non-volatile, protected by state reads/writes

// 保存异常结果
protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

// 保存正常结果
protected void set(V v) {
  if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    outcome = v;
    UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
    finishCompletion();
  }
}

setException() 和 set ()方法非常相似,都是将异常或者结果保存在 Object 类型的 outcome 变量中,outcome 是成员变量,就要考虑线程安全,所以他们要通过 CAS方式设置 outcome 变量的值,既然是在 CAS 成功后 更改 outcome 的值,这也就是 outcome 没有被 volatile 修饰的原因所在。

FutureTask#finishCompletion() 方法
这个方法是唤醒关注执行结果的线程,通知他们去获取任务的执行结果

private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            for (;;) {
                Thread t = q.thread;
                if (t != null) {
                    q.thread = null;
                   // 唤醒等待队列中的线程
                    LockSupport.unpark(t);
                }
                WaitNode next = q.next;
                if (next == null)
                    break;
                q.next = null; // unlink to help gc
                q = next;
            }
            break;
        }
    }

    done();

    callable = null;        // to reduce footprint
}  

3.4 任务的获取

get()方法

public V get() throws InterruptedException, ExecutionException {
    int s = state;
   // 如果 state 还没到 set outcome 结果的时候,则调用 awaitDone() 方法阻塞自己
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
   // 返回结果
    return report(s);
}

awaitDone(boolean timed, long nanos)阻塞线程并且加入阻塞队列中

// get 方法支持超时限制,如果没有传入超时时间,则接受的参数是 false 和 0L
// 有等待就会有队列排队或者可响应中断,从方法签名上看有 InterruptedException,说明该方法这是可以被中断的
private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
   // 计算等待截止时间
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
       // 如果当前线程被中断,如果是,则在等待对立中删除该节点,并抛出 InterruptedException
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
       // 状态大于 COMPLETING 说明已经达到某个最终状态(正常结束/异常结束/取消)
       // 把 thread 只为空,并返回结果
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
       // 如果是COMPLETING 状态(中间状态),表示任务已结束,但 outcome 赋值还没结束,这时主动让出执行权,让其他线程优先执行(只是发出这个信号,至于是否别的线程执行一定会执行可是不一定的)
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
       // 等待节点为空
        else if (q == null)
           // 将当前线程构造节点
            q = new WaitNode();
       // 如果还没有入队列,则把当前节点加入waiters首节点并替换原来waiters
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
       // 如果设置超时时间
        else if (timed) {
            nanos = deadline - System.nanoTime();
           // 时间到,则不再等待结果
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
           // 阻塞等待特定时间
            LockSupport.parkNanos(this, nanos);
        }
        else
           // 挂起当前线程,知道被其他线程唤醒
            LockSupport.park(this);
    }
}

进入这个方法会经历三个循环:

  • 第一轮for循环,执行的是q == null逻辑,这个时候会创建一个WaitNode节点
  • 第二轮for循环,执行的是!queued,这个时候会把第一轮循环生成的节点的next执行waiters,然后通过CAS的把节点q替换waiters,如果替换成功,第二轮结束
  • 第三轮for循环,执行阻塞,或者阻塞特定的时间
    设置返回结果(set方法)/异常的方法(setException)两个方法后,调用finishCompletion方法,这个方法会唤醒等待队列中的线程.

3.5 任务的取消

(1) 将一个任务修改为终态的只有三种方法:
set()方法
setException()方法
cancel 方法

查看 Future cancel(),该方法注释上明确说明三种 cancel 操作一定失败的情形

  • 任务已经执行完成了
  • 任务已经被取消过了
  • 任务因为某种原因不能被取消

其它情况下,cancel操作将返回true。值得注意的是,cancel操作返回 true 并不代表任务真的就是被取消, 这取决于发动cancel状态时,任务所处的状态

  • 如果发起cancel时任务还没有开始运行,则随后任务就不会被执行;
  • 如果发起cancel时任务已经在运行了,则这时就需要看 mayInterruptIfRunning 参数了:

如果mayInterruptIfRunning 为true, 则当前在执行的任务会被中断

如果mayInterruptIfRunning 为false, 则可以允许正在执行的任务继续运行,直到它执行完

public boolean cancel(boolean mayInterruptIfRunning) {
  
    if (!(state == NEW &&
          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {    // in case call to interrupt throws exception
       // 需要中断任务执行线程
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
               // 中断线程
                if (t != null)
                    t.interrupt();
            } finally { // final state
               // 修改为最终状态 INTERRUPTED
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
       // 唤醒等待中的线程
        finishCompletion();
    }
    return true;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值