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 的用法以及原理有太多人介紹過了,但這裡我還是會介紹
其用法,原理,並嘗試從不同的角度去分析,理解。
注: 文中如有有錯誤的,需要提高的程式碼,請您留言,我會及時改正。