Java源码学习--并发任务类02--01--FutureTask

Callable

  • Callable是一个接口,约定了线程要做的事情,和Runnable一样,区别在于有返回值
  • 代码
    public interface Callable<V> {
       V call() throws Exception;
    }
    

Runnable接口

  • 该接口指定了线程执行的任务,实现了此接口的类,即可作为线程执行的任务,进行执行
  • 代码
    public interface Runnable {
        public abstract void run();
    }
    

Future接口

  • 提供了对任务进行处理的方法,获取结果,取消任务
  • 特点
    1. 定义了异步计算的接口,提供了计算是否完成的检查,等待完成和取回等方法
    2. 提供了阻塞获取结构的get方法
    3. 取消可以使用cancel方法,但一旦计算完成,就无法取消
  • 源码
    public interface Future<V> {
        //取消方法
        //针对线程执行的状态,进行分析
        //1. 如果任务已经完成,会直接返回取消成功
        //2. 如果任务已经取消,会直接返回取消成功
        //3. 如果任务还没有开始,发起取消是,可以取消成功的
        //4. 如果取消时,任务已经在运行了,mayInterruptIfRunning为true的话,就可以打断运行中的线程,取消任务
        //5. 如果取消时,任务已经在运行了,mayInterruptIfRunning为false的话,表示不能打断直接返回false
        boolean cancel(boolean mayInterruptIfRunning);
    
        //判断是否取消
        boolean isCancelled();
    
        //线程是否已经结束
        boolean isDone();
    
        //阻塞获取返回结果
        //如果任务被取消了,返回CancellationException异常
        //如果等待过程中被打断了,抛InterruptedException异常
        V get() throws InterruptedException, ExecutionException;
    
    
        //等待,有超时时间的等待,如果超时之后仍然没有响应,抛TimeoutException异常
        V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
    }
    

RunnableFuture接口

  • 该接口继承了Future、Runnable接口,意味着实现该接口的类,可以作为线程任务执行,同时还具备了管理线程的能力
  • 通过jdk学习,可以看到将每个功能都合理的细分接口,实现接口就意味着实现了接口所代表的功能
  • 代码
    public interface RunnableFuture<V> extends Runnable, Future<V> {
        void run();
    }
    

FutureTask

  • FutureTask实现了Runnable接口,具备了管理任务的能力,同时内部组合了Callable实例,可以获取任务的返回值,那内部是如何处理Runnable和Callable
1 基本属性
  • 类定义
    // 任务状态
    private volatile int state;
    private static final int NEW          = 0;//线程任务创建
    private static final int COMPLETING   = 1;//任务执行中
    private static final int NORMAL       = 2;//任务执行结束
    private static final int EXCEPTIONAL  = 3;//任务异常
    private static final int CANCELLED    = 4;//任务取消成功
    private static final int INTERRUPTING = 5;//任务正在被打断中
    private static final int INTERRUPTED  = 6;//任务被打断成功
    
    // 组合了 Callable
    private Callable<V> callable;
    // 异步线程返回的结果
    private Object outcome;
    // 当前任务所运行的线程
    private volatile Thread runner;
    // 记录调用 get 方法时被等待的线程
    private volatile WaitNode waiters;
    
  • 构造器
    // 使用 Callable 进行初始化
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        // 任务状态初始化
        this.state = NEW;       // ensure visibility of callable
    }
    
    // 使用 Runnable 初始化,并传入 result 作为返回结果。
    // Runnable 是没有返回值的,所以 result 一般没有用,置为 null 就好了
    public FutureTask(Runnable runnable, V result) {
        // Executors.callable 方法把 runnable 适配成 RunnableAdapter,
        // RunnableAdapter 实现了 callable,所以也就是把 runnable 直接适配成了 callable。
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }
    
  • 适配器模式
    • FutureTask在构造方法时,使用了两种,一种是runnable,一种是callable,具体内部是使用的callable,而对于runnable而言,使用适配器模式,将runnable转换成一种callable类,进行统一使用。这样对外使用的时候,就不用区别使用runnable的方法还是callable的方法,统一归为callable的方法,一不小心学到了!!!
    • 代码
      public static <T> Callable<T> callable(Runnable task, T result) {
          if (task == null)
              throw new NullPointerException();
      
          //runnable适配器,转换成callable的子类
          return new RunnableAdapter<T>(task, result);
      }
      
      • 适配器类,实现了Callable接口,内部封装了Runnable对象
      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;
          }
          public T call() {
              task.run();
              return result;
          }
      }
      
2 重要方法
2.1 get获取返回值方法
  • 源码
    • 超时获取结果方法get
    //通过抛出的异常,可以看出,可以响应中断,可以响应超时
    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        //如果任务已经在执行,等待了超时时间之后,还在执行,保存超时异常
        if (s <= COMPLETING &&
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        //返回执行结果
        return report(s);
    }
    
    • awaitDone:等待超时时间获取结果
      • 一个新线程执行流程
        1. 第一轮for循环,执行的逻辑是q == null,所以这时候会新建一个节点q。第一轮循环结束。
        2. 第二轮for循环,执行的逻辑是!queue,这个时候会把第一轮循环中生成的节点的next指针指向waiters,然后CAS的把节点q替换waiters。也就是把新生成的节点添加到waiters链表的首节点。如果替换成功,queued=true。第二轮循环结束。
        3. 第三轮for循环,进行阻塞等待。要么阻塞特定时间,要么一直阻塞知道被其他线程唤醒。
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
    
        //1. 计算出终止时间
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
    
        //不排队
        boolean queued = false;
    
        //2. 自旋加入队列
        for (;;) {
    
            //2.1 如果线程已经中断,之前抛出中断异常
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }
    
            int s = state;
            //2.2 如果线程已经结束,直接返回状态
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            //2.3 如果正在执行,当前线程让出 cpu,重新竞争,防止 cpu 飙高
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            //2.4 默认第一次都会执行这里,执行成功之后,queued 就为 true,就不会再执行了,把当前 waitNode 当做 waiters 链表的第一个
            else if (q == null)
                q = new WaitNode();
            else if (!queued)
                //如果不在列表中,就把,q加入到链表的头部,让waiterOffset指向它
                //最新执行的线程,会加到头结点的地方
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            //2.5 如果设置了超时时间,并过了超时时间的话,从 waiters 链表中删除当前 wait
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                // 没有过超时时间,线程进入 TIMED_WAITING 状态
                LockSupport.parkNanos(this, nanos);
            }
            //2.6 没有设置超时时间,进入 WAITING 状态
            else
                LockSupport.park(this);
        }
    }
    
    • 看来report是通过获取状态,然后获取结果,又是一种解耦的操作,无敌的源码,看一波report获取结果的方法,直接通过结果,然后判断状态返回,感觉就是为了校验状态,然后返回结果,结果的获取,然而并不是真正获取结果的方法,难受
    private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }
    
  • get方法虽然是为了获取结果,但是做了很多wait的动作
    1. 如果发现任务正在进行,没有完成,就阻塞当前线程
    2. 任务执行完成后返回结果。阻塞底层使用的是LockSupport.park方法,使当前线程进入WAITING或TIMED_WAITING状态。
2.2 run:任务执行的方法
  • 类实现了Callable接口,也实现了Runnable接口,可想,该类肯定是作为任务执行,正如名称所示,所以任务最重的方法,是run方法,额,执行状态与任务解耦,与结果解耦
  • 代码
    public void run() {
        // 状态不是任务创建,或者当前任务已经有线程在执行了,直接返回
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    //调用执行
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                //将结果赋值给outcome值
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
    
2.3 cancel:取消任务
  • 代码
    // 取消任务,如果正在运行,尝试去打断
    public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&//任务状态不是创建 并且不能把 new 状态置为取消,直接返回 false
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        // 进行取消操作,打断可能会抛出异常,选择 try finally 的结构
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    //状态设置成已打断
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            // 清理线程
            finishCompletion();
        }
        return true;
    }
    
  • 清理线程的方法: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
    }
    
2.4 waiters
  • waiters用来记录需要获取此任务执行结果的线程,类似于栈结构,通过next形成链表的结构,因为一个任务可能多个线程都在执行,最新执行的线程会加入到头部
  • 节点的初始化,将当前的线程作为节点的线程属性,创建
    static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }
    

优秀文章:https://blog.csdn.net/Memery_last/article/details/79532630,包含图片

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值