• FutureTask(未来任务) 源码解析


    FutureTask(未来任务)

    一、前情回顾(重要)

    首先我们先回顾一下多线程创建的方式

    1. 直接继承Thread方式
    2. 实现Runnable 方式
    3. 实现Callable方式
    4. 线程池方式

    这四种方式主要分为两类:没返回值的(1,2) 有返回值的(3,4)

    没返回值的相信已经烂熟于心了。这次我们讲讲有返回值的,下面先给出3,4的两种创建多线程的示例:

    1.实现Callable方式

    public class CallableTest {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    		//创建任务
            FutureTask task = new FutureTask(() -> {
                System.out.println(Thread.currentThread().getName() + " is running");
                return 200;
            });
            
            //创建线程
            new Thread(task,"A").start();
    		//获得任务的返回值
            System.out.println(task.get());
        }
    }
    
    /**
    执行结果:
    	A is running
    	200
    **/
    

    提问?

    1.Thread为什么能接收FutureTask?
        
    2.为什么线程能执行Callable实现的call方法?
    

    2.线程池方式

    //实现Runnable
    class RunnableDemo implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " runnable is running");
        }
    }
    
    //实现Callable
    class CallableDemo implements Callable{
    
        @Override
        public Object call() throws Exception {
            System.out.println(Thread.currentThread().getName() + " callable is running");
            return 200;
        }
    }
    
    public class ThreadPoolTest {
        public static void main(String[] args) throws Exception {
            //创建线程池
            ExecutorService threadPool = Executors.newFixedThreadPool(5);
    		
            //提交并获得任务
            Future callableTask = threadPool.submit(new CallableDemo());
            Future runnableTask = threadPool.submit(new RunnableDemo(), 400);
    		
            //根据任务获得返回值
            System.out.println(callableTask.get());
            System.out.println(runnableTask.get());
    		
            //关闭线程池
            threadPool.shutdown();
        }
    }
    
    /**
    	pool-1-thread-1 callable is running
        200
        pool-1-thread-2 runnable is running
        400
    **/
    

    提问?

    1. 为何线程池中能传入Callable 或 Runnable的实现?
        
    2. submit的方法返回的Future类是什么?
    
    3. 为何也都可以根据任务获取返回值,且Runnable也有返回值?
    

    你在学习以上两种方式创建多线程的时候是否会有这些疑问呢,根据上面的几个提问,来一点点获取线索,相信自己最终可以破案。

    3.获取线索

    我们首先要找出 实现Callable方式线程池方式 两者的共同点。

    • 实现Callable方式new FutureTask()

    • 线程池的方式 这里我们要先看一下submit() 方法做了什么?

    public interface ExecutorService extends Executor{
        <T> Future<T> submit(Callable<T> task);
        <T> Future<T> submit(Runnable task, T result);
        Future<?> submit(Runnable task);
    }
    

    不难发现,ExecutorService是一个接口,那么就直接去看他的一个实现类

    public abstract class AbstractExecutorService implements ExecutorService {
        
        //提交Runnable实现 不带任务返回值
        public Future<?> submit(Runnable task) {
            if (task == null) throw new NullPointerException();
            RunnableFuture<Void> ftask = newTaskFor(task, null);
            execute(ftask);
            return ftask;
        }
    	//提交Runnable实现 带任务返回值
        public <T> Future<T> submit(Runnable task, T result) {
            if (task == null) throw new NullPointerException();
            RunnableFuture<T> ftask = newTaskFor(task, result);
            execute(ftask);
            return ftask;
        }
    	
        //提交Callable实现 
        public <T> Future<T> submit(Callable<T> task) {
            if (task == null) throw new NullPointerException();
            RunnableFuture<T> ftask = newTaskFor(task);
            execute(ftask);
            return ftask;
        }
    }
    

    有三个submit() 的重载方法,其中有个共同的方法就是newTaskFor()submit的传参的值原封不动的传了进去,且返回了个 RunnableFuture ,那么直接来看这个newTaskFor()

        protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
            return new FutureTask<T>(runnable, value);
        }
    
        protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
            return new FutureTask<T>(callable);
        }
    

    惊奇的发现它内部也是 new FutureTask()

    那么接下来就来到了我们今天的主题 解密 FutureTask

    二、继承与实现

    public class FutureTask<V> implements RunnableFuture<V> 
    
    public interface RunnableFuture<V> extends Runnable, Future<V> 
    

    FutureTask的实现关系来看,它实际上是实现了 RunnableFuture 这两个接口

    这里也就解释了之前的一个问题:

    问:Thread为什么能接收FutureTask?
        
    答:因为FutureTask实现了Runnable接口。
    

    三、构造方法

        //接收Callable实现的构造
    	public FutureTask(Callable<V> callable) {
            if (callable == null)
                throw new NullPointerException();
            //赋值 Callable的实现 
            this.callable = callable;
            //将当前任务标记为 NEW(任务尚未执行)
            this.state = NEW;       // ensure visibility of callable
        }
    
    	//接收Runnable实现的构造
        public FutureTask(Runnable runnable, V result) {
            //将Runnable包装成Callable
            this.callable = Executors.callable(runnable, result);
            //将当前任务标记为 NEW(任务尚未执行)
            this.state = NEW;       // ensure visibility of callable
        }
    

    接收Runnable实现的构造中,使用了适配器模式Runnable包装成Callable

        public static <T> Callable<T> callable(Runnable task, T result) {
            if (task == null)
                throw new NullPointerException();
            //使用的是适配器模式将runnable转换为了 callable接口,外部线程 通过get获取当前任务执行结果时
        	//结果可能为null 也可以能为 传进来的result值
            return new RunnableAdapter<T>(task, result);
        }
    
    	//实现了Callable,并实现了
        static final class RunnableAdapter<T> implements Callable<T> {
            final Runnable task;
            final T result;
            RunnableAdapter(Runnable task, T result) {
                //将runnable的实现赋值给task
                this.task = task;
                this.result = result;
            }
            public T call() {
                task.run();
                return result;
            }
        }
    

    RunnableAdapter实现了Callable 并在实现call()方法中调用了Runnable实现的run()方法。这样就成功将Runnable 包装成了 Callable

    四、成员变量及内部类

        //当前任务的状态
    	private volatile int state;
    	
    	//以下7个对应任务有哪些状态
    	
    	//当前任务尚未执行
        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;
    
    	//构造方法中传进来的 runnable/callable, 
    	//callable直接赋值
    	//runnable 经过 RunnableAdapter包装成Callable后赋值
        private Callable<V> callable;
    	
    	//存放任务的执行结果,有两种结果
    	//正常情况下,任务正常执行结束, outcome保存 callable的call方法的返回值
    	//异常情况下,callable的 call方法向上抛出异常,outcome保存异常
        private Object outcome;
    	
    	//当前任务被线程执行期间,保存当前执行任务的线程的对象引用,有且仅有一个线程。
        private volatile Thread runner;
    	
    	//因为会有很多线程来get当前任务的结果, 所以这里使用了链表,来存放这些线程。
        private volatile WaitNode waiters;
    
        static final class WaitNode {
            volatile Thread thread;
            volatile WaitNode next;
            WaitNode() { thread = Thread.currentThread(); }
        }
    

    五、成员方法

    看下面的代码时,需要规定一个场景

    场景 : 多线程并发

    1.执行入口run()方法

    为什么说run()是执行入口?

    当我们启动线程时,最基本的就是new Thread().start()
    start()内部实际上又调用了 native start0() 方法
    通过操作系统为我们创建一个线程,并调用run() 方法。
    最终会执行 target.run() 也就会调用Runnable实现的run()方法 (详情查看Thread源码)

    由于FutureTaskRunnable的实现,因此该 run()方法是执行入口。

        public void run() {
            
            /**
            	判断当前任务状态是否为未执行
            	state != NEW  
                
                线程池中的所有线程会争抢该task任务,
                通过CAS控制并发,赋值runner属性,也就意味着只有一个线程能够抢到该任务去执行
            	!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread())
            **/
            //该判断的目的主要是为了保证只能有一个线程来执行该任务。
            if (state != NEW ||
                !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                             null, Thread.currentThread()))
                return;
            
            //执行到这里,当前task一定是NEW状态,且只有一个当前线程抢占成功,runner属性是当前线程的引用。
            try {
                //callable就是程序员自己实现的callable/通过装饰后的runnable(RunnableAdapter)
                Callable<V> c = callable;
                
                //相当于是一个DCL
                //条件一:c != null  防止空指针
                //条件二:state==NEW 防止被其它线程Cancel了,所以再次确认下任务状态  
                if (c != null && state == NEW) {
                    
                    //执行call的返回结果的引用
                    V result;
                    
                    //true  表示callable.call 执行成功 未抛出异常
                    //false 表示callable.call 执行失败 抛出异常
                    boolean ran;
                    try {
                        //执行程序员实现的业务逻辑
                        result = c.call();
                        //若没抛异常,则设置为true
                        ran = true;
                    } catch (Throwable ex) {
          				//若抛异常,则设置为false,返回值也置为null
                        result = null;
                        ran = false;
                        //TODO 后面会说... (透露:会将异常信息设置到outcome中)
                        setException(ex);
                    }
                    //如果为true 代表执行成功,
                    if (ran)
                        //TODO 后面会说...  (透露:会将成功返回值结果设置到outcome中)
                        set(result);
                }
            } finally {
    			//将执行当前任务的线程引用置为null, 表示该任务没有线程对其进行执行了
                runner = null;
                
                // 判断当前任务状态是不是中断状态
                int s = state;
                if (s >= INTERRUPTING)
                    //如果是中断状态,那么该任务会一直死循环在该处,直到由外部对其进行cancel 或者 unpark
                    handlePossibleCancellationInterrupt(s);
            }
        }
    

    下面是 run()中调用的方法

        protected void set(V v) {
            
            //使用CAS方式,设置当前任务状态完成中...
            //有没有可能失败呢? 外部线程等不及了,直接在set执行CAS前,将task取消了
            if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
                //赋值正常返回值
                outcome = v;
                //结果赋值结束后,马上会将当前任务状态修改为 NORMAL状态 表示正结束
                UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
                //TODO 该方法后面再说...
                finishCompletion();
            }
        }
    
        protected void setException(Throwable t) {
            //使用CAS方式,设置当前任务状态完成中...
            //有没有可能失败呢? 外部线程等不及了,直接在setException执行CAS前,将task取消了
            if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
               //赋值异常信息
                outcome = t;
                //将当前任务状态修改为 EXCEPTIONAL状态 表示异常状态
                UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
                //TODO 该方法后面再说...
                finishCompletion();
            }
        }
    
        private void handlePossibleCancellationInterrupt(int s) {
    		
            //若任务状态是中断中... 则一直在此死循环,且一直释放CPU,直到该状态不是中断中...
            if (s == INTERRUPTING)
                while (state == INTERRUPTING)
                    Thread.yield(); // wait out pending interrupt
    
            // assert state == INTERRUPTED;
    
            // We want to clear any interrupt we may have received from
            // cancel(true).  However, it is permissible to use interrupts
            // as an independent mechanism for a task to communicate with
            // its caller, and there is no way to clear only the
            // cancellation interrupt.
            //
            // Thread.interrupted();
        }
    

    2.获取任务结果get()方法

    get()方法有两种,一种是不带超时的,一种是带超时的。这里我们主要看不带超时的。

        public V get() throws InterruptedException, ExecutionException {
            //获得当前任务状态
            int s = state;
            
            //条件成立:当前任务状态为 NEW,COMPLETING 表示当前任务还未执行或正在完成中..
            if (s <= COMPLETING)
                
                //所有调用get()方法的线程 会被阻塞在此
                //直到会得到结果(任务的状态返回值) 或者 向上抛出中断异常
                s = awaitDone(false, 0L);
            
            //返回 结果或者抛异常
            return report(s);
        }
    
        private int awaitDone(boolean timed, long nanos)
            throws InterruptedException {
            
            //timed = false
            //deadline = 0L
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            
            //引用当前线程 封装成WaitNode对象
            WaitNode q = null;
            
            //表示当前get()的线程有没有入队
            boolean queued = false;
            
            //自旋
            for (;;) {
                //条件成立: 当前执行get的线程被interrupt 叫醒
                if (Thread.interrupted()) {
                    //将引用当前线程的WaitNode对象 移除队,且在移除的过程中同样会移除其它q=null的节点对象
                    removeWaiter(q);
                    //向上抛出中断的异常
                    throw new InterruptedException();
                }
    			
                //获取当前任务的状态
                int s = state;
                
                //条件成立:说明当前任务已经有结果了,可能是好结果,也可以能使抛出异常的结果
                if (s > COMPLETING) {
                    
                    //条件成立:说明当前线程已经创建过WaitNode节点,
                    if (q != null)
                        
                        //此时由于得到了结果,需要将q.thread = null  
                        //helpGC 或者 当有线程执行removeWaiter方法时 会主动将该节点移除 
                        q.thread = null      
                        
                        
                    //直接返回当前任务的状态
                    return s;
                }
                
            //条件成立:说明当前任务正在完成中,还差赋值和设置成NORMAL或者EXCEPTIONAL两步
                else if (s == COMPLETING) // cannot time out yet
           //由于任务快要完成了,因此主动释放CPU,进行下一次CPU的抢占,目的是尽量让执行任务的线程去抢到CPU
                    Thread.yield();
                
                //条件成立: 第一次自旋,当前任务是NEW状态,当前线程还未创建WaitNode对象
                else if (q == null)
                    
                    //为当前线程创建WaitNode对象
                    q = new WaitNode();
                
          //条件成立:第二次自旋,当前任务是NEW状态,当前线程已经创建WaitNode对象,但是WaitNode对象还为入队
                else if (!queued)
                    
                    //头插法入队
                    queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                         q.next = waiters, q);
         //条件成立:第三次自旋 当前任务是NEW状态,由于规定timed=false,则不会进入这个判断,会进入下面的else        //也就意味着 如果是带超时的会走当前判断, 不带超时的会走下一个else判断
                else if (timed) {
                    nanos = deadline - System.nanoTime();
                    if (nanos <= 0L) {
                        removeWaiter(q);
                        return state;
                    }
                    LockSupport.parkNanos(this, nanos);
                }
                
                //不带超时的
                //当前get操作的线程就会被park,线程会进入waiting状态 休眠
                //除非有其它线程将其unpark(thread) 唤醒 或者 将当前线程中断 才会进入下一次自旋
                else
                    LockSupport.park(this);
            }
        }
    

    返回给用户结果的方法,该结果可能是好结果,也可能是抛异常的结果

        private V report(int s) throws ExecutionException 
            //outcome 保存的是Callable运行结束的结果,可能是正常的好结果,也可以能是异常的信息
            Object x = outcome;
    
    		//条件成立:当前任务状态正常结束
            if (s == NORMAL)
                //直接返回好结果
                return (V)x;
    
    		//条件成立:任务被取消 或 中断
            if (s >= CANCELLED)
                //抛出异常
                throw new CancellationException();
    		//执行到这 说明任务状态为EXCEPTIONAL 程序员的Callable实现的代码有bug了
            throw new ExecutionException((Throwable)x);
        }
    

    3.finishCompletion()方法

        private void finishCompletion() {
            // assert state > COMPLETING;
            
            //循环Waiters链表
            for (WaitNode q; (q = waiters) != null;) {
                
                            
                //使用CAS设置Waiters为null,是为了防止恰好有新的线程节点入队,正好可以带该线程节点
                //同时也会使之后去get()的线程 新生成一个新的队列
                if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                    for (;;) {
                        
                       	//获取当前Node节点封装的thread
                        Thread t = q.thread;
                        
                        //条件成立:说明当前下次呢很难过不会为null
                        if (t != null) {
                            
                            //help GC
                            q.thread = null;
                           	//唤醒get()阻塞的的线程
                            LockSupport.unpark(t);
                        }
                        WaitNode next = q.next;
                        //如果到链表末尾了就退出循环
                        if (next == null)
                            break;
                        q.next = null; // unlink to help gc
                        q = next;
                    }
                    break;
                }
            }
    		//可扩展的点
            done();
    		
            //将callable 设置为null  help GC
            callable = null;        // to reduce footprint
        }
    

    4.取消任务 cancel()方法

        public boolean cancel(boolean mayInterruptIfRunning) {
            
    		//也就意味着 当前任务状态如果是NEW 不允许被cancel 将状态改为INTERRUPTING 或者CANCELLED
            if (!(state == NEW &&
                  UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                      mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
                return false;
            
            //执行到这就说明 当前任务是从非NEW状态 被改为了INTERRUPTING 或CANCELLED 的
            //mayInterruptIfRunning 
            //        true: INTERRUPTING  false:CANCELLED
            try {   
                //如果是true 也就是状态改成了INTERRUPTING
                if (mayInterruptIfRunning) {
                    try {
                        //将执行当前任务的线程取出来
                        Thread t = runner;
                       	//判断是否有线程执行该任务
                        if (t != null)
                            //有的话  则中断该线程
                            t.interrupt();
                    } finally { // final state
                        //最终将该任务的状态设置为 INTERRUPTED
                        UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                    }
                }
            } finally {
                //然后唤醒所有执行get()阻塞的线程
                finishCompletion();
            }
            return true;
        }
    
    万般皆下品,唯有读书高!
  • 相关阅读:
    Joshua Bloch错了? ——适当改变你的Builder模式实现
    集成基于OAuth协议的单点登陆
    集成基于CAS协议的单点登陆
    数据库设计中的Soft Delete模式
    完成C++不能做到的事
    ExtJS in Review
    DTO – 服务实现中的核心数据
    保存好你的密码 —— 从芝麻金融被攻破说起
    WPF
    C# 反编译防范
  • 原文地址:https://www.cnblogs.com/s686zhou/p/15057322.html
Copyright © 2020-2023  润新知