AsyncTask分析(二)---Future、Callable、FutureTask

AsyncTask分析(二)—Future、Callable、FutureTask

之前分析AsyncTask源碼的時候,在其中提到了Future,Callable,FutureTask三個類或接口,今天就揭開他們的神祕面紗,探究他們的運用。

先看一下Future接口源碼:

//Future是一個接口,他提供給了我們方法來檢測當前的任務是否已經結束
//,還可以等待任務結束並且拿到一個結果,說白了他可以用來進行線程同步
public interface Future<V> {
    //如果任務已經完成或者已經停止了或者這個任務無法停止,則會返回一個false
    boolean cancel(boolean mayInterruptIfRunning);
    //判斷任務是否取消,
    boolean isCancelled();
    //判斷任務是否完成
    boolean isDone();
    //取值,任務未完成之前,阻塞當前線程,直到任務完成,
    //如果已經調用cancel(),在調用gete,會拋異常
    V get() throws InterruptedException, ExecutionException;
    //設置最大等待時間(線程阻塞最長時間),其餘同get()
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

幹說,可能不太好理解,我們來個Demo,測試一下

private static void executor() throws Exception{
        ExecutorService service = Executors.newSingleThreadExecutor();
        CountRunnable count = new CountRunnable();
        Future f = service.submit(count);
        long start = System.currentTimeMillis();
        System.out.println("start:"+start);
        try{
            System.out.println("result:"+f.get(10*1000, TimeUnit.MILLISECONDS));
        }catch(Exception e){
            System.out.println(e.toString());
        }finally{
            service.shutdown();
        }
        long end = System.currentTimeMillis();
        System.out.println("任務結束 end:"+end+",共耗時:"+(end-start));
    }


    static class CountRunnable implements Runnable{

         private int sum;
            @Override
            public void run() {
                for(int i=1 ; i<11 ; i++){
                    sum = sum+i;
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("run 結束  sum="+sum);
            }

    }


    public static void main(String[] args) {
        try {
            executor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

看下打印結果:
QQ截圖20161216114031.png-9.9kB

Future.get()方法的的確確是阻塞了當前線程(因此如果在Android上使用Future不能再UIThread調用Future.get();),任務線程的完成時間是20s,但是我們的get()設置的阻塞時間是10s,在10s內無法返回結果,就會報TimeOut異常。爲什麼 任務線程最後輸出還是sum=55?那是因爲我們並沒有取消任務,任務繼續執行。

繼續來看Callable:

//同樣是一個泛型接口,只有一個call()
public interface Callable<V> {
    V call() throws Exception;
}

這個是用來做什麼的呢?看過文檔註釋就會明白,在普通的Runnable執行過程中,也就是run()執行是沒有返回值的,那我們也就無法判斷任務的執行情況,也沒法決定在什麼時候停止任務,取消任務,但是Callable call()就給我們提供了這個判斷。

看一個Demo

static void callTest(){
        ExecutorService pool = Executors.newSingleThreadExecutor();
        CountCallable count = new CountCallable();
        Future<Number> f = pool.submit(count);
        long start = System.currentTimeMillis();
        System.out.println("start:"+start);
        try{
            System.out.println("result:"+f.get().number);
        }catch(Exception e){
            System.out.println(e.toString());
        }finally{
            pool.shutdown();
        }
        long end = System.currentTimeMillis();
        System.out.println("任務結束 end:"+end+",共耗時:"+(end-start));
    }


    static class CountCallable implements Callable<Number>{

        @Override
        public Number call() throws Exception {
            Number number = new Number();
            number.setNumber(100);
            System.out.println("call...");
            TimeUnit.SECONDS.sleep(2);
            return number;
        }

    }

    static class Number{
        public int number;

        public void setNumber(int number){
            this.number = number;
        }
    }

看下結果:
QQ截圖20161216122851.png-5.8kB
當pool.submit()之後,就會立即回調call(),當任務完成之後,就會返回我們在call()中設置的Number值。

剛剛說了,Future還有個功能是取消任務,不妨來試一下:

static void cancelTest() throws Exception{
        ExecutorService pool = Executors.newSingleThreadExecutor();
        CountCallable count = new CountCallable();
        Future<Number> f = pool.submit(count);
        System.out.println("任務開始於:"+System.currentTimeMillis());
        TimeUnit.SECONDS.sleep(4);
        f.cancel(true);
        if(f.isCancelled()){
            System.out.println("任務被取消於:"+System.currentTimeMillis());
        }else{
            Number b = f.get();
            System.out.println("任務繼續執行:");
            if(f.isDone()){
                System.out.println("任務執行完畢:"+System.currentTimeMillis());
                pool.shutdown();
            }
        }

    }

看一下打印結果:
QQ截圖20161216133854.png-4.5kB
,cancel()之後,是不能再調get(),不然會報異常。
到這裏Future和Callable基本就介紹完了,下面再看一下FutureTask

FutureTask登場

Future是一個接口,他的唯一實現類就是FutureTask,其實FutureTask的一個很好地特點是他有一個回調函數done()方法,當一個任務執行結束後,會回調這個done()方法,我們可以在done()方法中調用FutureTask的get()方法來獲得計算的結果。爲什麼我們要在done()方法中去調用get()方法呢? 這是有原因的,我在Android開發中,如果我在主線程去調用futureTask.get()方法時,會阻塞我的UI線程,如果在done()方法裏調用get(),則不會阻塞我們的UI線程。

先看一下

public class FutureTask<V> implements RunnableFuture<V>{}

public interface RunnableFuture<V> extends Runnable, Future<V> {}

這就知道了FutureTask,即可作爲Runnable,也可作爲Future;

那就看下FutureTask中的代碼:

 public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, 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);
                }
                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);
        }
    }

        protected void setException(Throwable t) {
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
            outcome = t;
            U.putOrderedInt(this, STATE, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

當執行FutureTask中的run()時,真正起作用的其實是Callable接口在調用call(),這裏最終也是調用了finishCompletion();

再來看一段:

 public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&
              U.compareAndSwapInt(this, STATE, 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
                    U.putOrderedInt(this, STATE, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }


     private void finishCompletion() {
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
            if (U.compareAndSwapObject(this, WAITERS, 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
    }

這裏我們看到,當調用cancel(boolean)後,如果mayInterruptIfRunning = true,就會去執行t.interrupt();不論成功與否,最後執行finishCompletion(),執行done(),置空callable;
所以不論是cancel(返回true的時候),還是run最終都會調用done()

來個Demo驗證下:

static class CountCallable implements Callable<Number>{

        @Override
        public Number call() throws Exception {
            Number n = new Number();
            n.number = 100;
            System.out.println("call....");
            TimeUnit.SECONDS.sleep(10);
            return n;
        }

    }

    static class Number{
        public int number;
    }

    FutureTask<Number> task ;
    ExecutorService es;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CountCallable callable = new CountCallable();
        task = new FutureTask<Number>(callable){

            @Override
            protected void done() {
                try {
                    Number result = task.get();
                    System.out.println("任務結束  number:"+result.number+",結束:"+System.currentTimeMillis()+",isDone:"+task.isCancelled()+",isCancel:"+task.isCancelled()+",theadName:"+Thread.currentThread().getName());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        };

        es = Executors.newFixedThreadPool(2);
        es.execute(task);
        System.out.println("任務開始:"+System.currentTimeMillis());

        findViewById(R.id.btn).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                if(task.isDone()||task.isCancelled()){
                    return;
                }
                System.out.println(task.cancel(true)+",isCanceled:"+task.isCancelled());

            }
        });
    }

看下結果:
1,不點擊cancel:
QQ截圖20161216151254.png-15.9kB
爲毛要在done()裏面調用get(),因爲done()是運行在子線程的,不會阻塞UIThread,
2,點擊cancel:
image_1b437ncgfbfe1rr657414ul1ia51p.png-15kB
task.cancel()返回true;

那在回過頭來看下AsyncTask中的運用:

 public AsyncTask() {
        //Callable接口的實現類,也就是FutureTask run()中真正工作的部分
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                // 暴露到程序中的就是doInBackGround();
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                //這裏獲取的就是call()中的result
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }


        public final boolean cancel(boolean mayInterruptIfRunning) {
        mCancelled.set(true);
        //同樣交給了Future去處理
        return mFuture.cancel(mayInterruptIfRunning);
    }

我們重點追蹤execute()這個流程:

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

      @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        //這裏其實就是SerialExecutor的execute()
        exec.execute(mFuture);

        return this;
    }

     private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
       public static final Executor SERIAL_EXECUTOR = new SerialExecutor();


    private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

一步一步追蹤下來,發現最終都是在SerialExecutor裏面了,exec.execute(mFuture)其實就是SerialExecutor執行的execute(mFuture);
這裏面我們看到直接調用了mFuture.run();

執行的流程圖如下
這裏寫圖片描述

參考:
Android併發編程之白話文詳解Future,FutureTask和Callable

發佈了91 篇原創文章 · 獲贊 37 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章