1. 程式人生 > >Android 併發二三事之利用CountDownLatch 或 ConditionVariable實現自定義Future

Android 併發二三事之利用CountDownLatch 或 ConditionVariable實現自定義Future

前言:

Android 併發第三篇
介紹如何利用 CountDownLatch 或 ConditionVariable 實現自定義Future,用於適應專案中的需求。
即阻塞當前執行緒,等待其他執行緒的結果返回,其功能類似於FutureTask。
首先介紹 CountDownLatch(共享鎖 Java)以及 ConditionVariable(Android)。

一、CountDownLatch

1、CountDownLatch 簡介:
CountDownLatch這個類能夠使一個執行緒等待其他執行緒完成各自的工作後再執行。

CountDownLatch是通過“共享鎖”實現的。在建立CountDownLatch中時,會傳遞一個int型別引數count,該引數是“鎖計數器”的初始狀態,表示該“共享鎖”最多能被count個執行緒同時獲取。當某執行緒呼叫該CountDownLatch物件的await()方法時,該執行緒會等待“共享鎖”可用時,才能獲取“共享鎖”進而繼續執行。而“共享鎖”可用的條件,就是“鎖計數器”的值為0!而“鎖計數器”的初始值為count,每當一個執行緒呼叫該CountDownLatch物件的countDown()方法時,才將“鎖計數器”-1;通過這種方式,必須有count個執行緒呼叫countDown()之後,“鎖計數器”才為0,而前面提到的等待執行緒才能繼續執行!

CountDownLatch 本身是基於AQS實現的,具體其原理,這裡不做太多介紹。

2、方法簡介:

CountDownLatch(int count)
構造一個用給定計數初始化的 CountDownLatch。這個值只能被設定一次,而且CountDownLatch沒有提供任何機制去重新設定這個計數值。

// 使當前執行緒在鎖存器倒計數至零之前一直等待,除非執行緒被中斷。
void await()
// 使當前執行緒在鎖存器倒計數至零之前一直等待,除非執行緒被中斷或超出了指定的等待時間。
boolean await(long timeout, TimeUnit unit)
// 遞減鎖存器的計數,如果計數到達零,則釋放所有等待的執行緒。
void countDown()
// 返回當前計數。
long getCount()

3、使用示例:

private void executeAfterWorker() {
        try {
            Log.d(TAG, "execute.....................");
            CountDownLatch countDownLatch = new CountDownLatch(2);
            Worker worker1 = new Worker(countDownLatch);
            Worker worker2 = new Worker(countDownLatch);
            worker1.start();
            worker2.start();
            Log.d(TAG, "execute wait"
); //等待其他的執行緒執行完。 countDownLatch.await(); //其他執行緒執行完後,在執行其他的操作 Log.d(TAG, "execute finish......"); } catch (Exception e) { } } private static class Worker extends Thread { private CountDownLatch countDownLatch; public Worker(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { try { Thread.sleep(100); Log.d(TAG, Thread.currentThread().getId() + " run"); } catch (Exception e) { } finally { //呼叫countDown()計數器減一 countDownLatch.countDown(); } } }

4、結果:

 D/Demo: execute.....................
 D/Demo: execute wait
 D/Demo: 16090 run
 D/Demo: 16091 run
 D/Demo: execute finish......

5、利用CountDownLatch 自定義Future :

功能介紹:在子執行緒中開啟其他執行緒聯網請求資料,
阻塞當前執行緒,等待結果返回,或者超時。

//請求資料
private void request() {
        new Thread(new Runnable() {
            @Override
            public void run() {

                try{
                    ResultFuture future = new ResultFuture(1);
                    request(future);
                    com.loader.demo.ResponInfo responInfo= future.get();
                    Log.d(TAG, responInfo.getName() + "requestAd");
                }catch (Exception e){

                }
            }
        }).start();

    }

    /**
     * 模擬聯網等耗時操作
     * @param resultListener
     */
    private void request(final ResultListener resultListener) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    Thread.sleep(3000);
                }catch (Exception e){

                }
                //模擬成功後回撥
                resultListener.onSuccess(new com.loader.demo.ResponInfo("BMW", 2000));
            }
        }).start();


    }
//資料實體
    public class ResponInfo {

        private String name;
        private long price;

        public ResponInfo(String name, long price) {
            this.name = name;
            this.price = price;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public long getPrice() {
            return price;
        }

        public void setPrice(long price) {
            this.price = price;
        }
    }
/**
 * 自定義Future。
 * 實現Future介面,提供get() 相關方法。
 * 實現ResultListener 介面,獲取成功或失敗的回撥資訊。
 *
 */
public class ResultFuture implements Future<ResponInfo>, ResultListener{

    private CountDownLatch mCountDownLatch;
    private ResponInfo mResponInfo;
    private boolean mResult = false;

    public ResultFuture(int count) {
        //開啟count 個執行緒,等待 count 個執行緒執行完或超時,才會返回結果。
        mCountDownLatch = new CountDownLatch(count);
    }


    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        return false;
    }

    @Override
    public boolean isCancelled() {
        return false;
    }

    @Override
    public boolean isDone() {
        return false;
    }

    @Override
    public ResponInfo get() throws InterruptedException, ExecutionException {
        try {
            return doGet(null);
        }catch (Exception e){
            return null;
        }

    }

    @Override
    public ResponInfo get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        return doGet(TimeUnit.MILLISECONDS.convert(timeout, unit));
    }

    private ResponInfo doGet(Long time) throws InterruptedException, ExecutionException, TimeoutException{
        //如果已經有結果,在直接返回
        if(mResult) {
            return mResponInfo;
        }
        //阻塞當前執行緒,直到呼叫mCountDownLatch.countDown();或者超時。
        if(time == null) {
            mCountDownLatch.await();
        }else {//加入超時時間
            mCountDownLatch.await(time, TimeUnit.MILLISECONDS);
        }

        if(!mResult) {
            //超時丟擲異常
            throw new TimeoutException();
        }

        return mResponInfo;
    }

    @Override
    public void onSuccess(ResponInfo responInfo) {

        //成功,將結果賦值給mResponInfo,呼叫countDown()方法,計數器減一
        this.mResponInfo = responInfo;
        mResult = true;
        mCountDownLatch.countDown();
    }

    @Override
    public void onFail() {
        //失敗,計數器減一
        mCountDownLatch.countDown();
    }
}
/**
 * 聯網結果回撥介面
 */
public interface ResultListener {

    void onSuccess(ResponInfo responInfo);

    void onFail();
}

二、ConditionVariable:

1、ConditionVariable 簡介:
ConditionVariable類位於android.os.ConditionVariable,它可以幫助Android執行緒同步。

其內部的實現的就是呼叫了wait()以及notifyAll();

之前介紹的 CountDownLatch 是Java 的類。而 ConditionVariable 是Android特有的。CountDownLatch 是共享鎖,
當前執行緒阻塞,要等待其他的執行緒都執行完,也就是計數器等於0時,才會向下執行。

而ConditionVariable 是Android 對 wait() , notifyAll()的封裝,呼叫block()方法阻塞當前執行緒,等待被喚醒。

2、構造方法:

    public ConditionVariable()
    {
        mCondition = false;
    }

    public ConditionVariable(boolean state)
    {
        mCondition = state;
    }

預設mCondition 即為false。一般會用無參的構造方法。

3、方法介紹:
ConditionVariable為我們提供以下幾個方法:
//釋放所有被阻塞的執行緒,任何執行緒在呼叫open()後呼叫block()都不會生效,除非先呼叫了close() ,後呼叫block()。
public void open()
//將條件(condition)重置為關閉狀態
public void close()
//阻塞當前執行緒,直到條件(condition)被開啟。如果條件(condition)本來是開啟的,將不會生效,會立即返回。
public void block()
//阻塞當前執行緒,直到條件(condition)被開啟,或者超時。如果條件(condition)本來是開啟的,將不會生效,會立即返回。
public boolean block(long timeout)

4、使用例項:

    private void executeAfterWorker() {
        try {
            Log.d(TAG, "execute.....................");
            ConditionVariable conditionVariable = new ConditionVariable();
            Worker worker = new Worker(conditionVariable);
            worker.start();
            Log.d(TAG, "execute wait");
            conditionVariable.block();
            Log.d(TAG, "execute finish......");
        } catch (Exception e) {
        }
    }

    private static class Worker extends Thread {

        private ConditionVariable conditionVariable;
        public Worker(ConditionVariable conditionVariable) {
            this.conditionVariable = conditionVariable;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(100);
                Log.d(TAG, Thread.currentThread().getId() + " run");

            } catch (Exception e) {

            } finally {
                conditionVariable.open();
            }
        }
    }

5、結果:

 D/Demo: execute.....................
 D/Demo: execute wait
 D/Demo: 16094 run
 D/Demo: execute finish......

6、下面來看如何利用ConditionVariable 打造自定義的Future。

在這裡就給出Future 的實現類,其他的程式碼和 CountDownLatch 的都一樣。

public class ResultFuture implements Future<ResponInfo>, ResultListener  {

    private ResponInfo mResponInfo;
    private boolean mResult = false;
    private ConditionVariable conditionVariable;

    public ResultFuture() {
        conditionVariable = new ConditionVariable();
    }


    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        return false;
    }

    @Override
    public boolean isCancelled() {
        return false;
    }

    @Override
    public boolean isDone() {
        return false;
    }

    @Override
    public ResponInfo get() throws InterruptedException, ExecutionException {
        try {
            return doGet(null);
        }catch (Exception e){
            return null;
        }

    }

    @Override
    public ResponInfo get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        return doGet(TimeUnit.MILLISECONDS.convert(timeout, unit));
    }

    private ResponInfo doGet(Long time) throws InterruptedException, ExecutionException, TimeoutException{
        //如果已經有結果,在直接返回
        if(mResult) {
            return mResponInfo;
        }
        if(time == null) {
            conditionVariable.block();
        }else {//加入超時時間
            conditionVariable.block(time);
        }

        if(!mResult) {
            //超時丟擲異常
            throw new TimeoutException();
        }

        return mResponInfo;
    }

    @Override
    public void onSuccess(ResponInfo responInfo) {

        this.mResponInfo = responInfo;
        mResult = true;
        conditionVariable.open();
    }

    @Override
    public void onFail() {
        conditionVariable.open();
    }

}

OK, 利用 CountDownLatch 或 ConditionVariable 實現自定義的Future,就介紹到這裡。其實原理非常的簡單。就是需要當前的執行緒等待結果。
或者超時。

之所以介紹 CountDownLatch 和 ConditionVariable 是因為這兩個比較熟悉。哈哈。在專案中都用到過多次。其中利用ConditionVariable 寫過
和Volley想結合的程式碼。就是編寫一個類實現Future,也同時實現Volley 訪問網路成功和失敗的結果。利用ConditionVariable 實現阻塞,等待結果。
感覺用起來非常的舒服。當然在 ResultFuture 編寫的還相對簡單,在其中我們還可以完善 isDone() 等方法。

下一篇開始將會介紹Android方面用於多執行緒方面的類,第一個是 AsyncTask , AsyncTask 的用法以及原理有太多人介紹過了,但這裡我還是會介紹
其用法,原理,並嘗試從不同的角度去分析,理解。

注: 文中如有有錯誤的,需要提高的程式碼,請您留言,我會及時改正。