我們在開發過程中,會有這樣的場景:
我有兩個線程,一個線程去獲取一個網絡數據,我要等待兩個線程回來之後,結合他們的數據去展示到ui裏面。怎麼實現:
面試被問到這個問題,我的回答是:
設置兩個boolean 變量,一個線程OK 之後,就把這個線程的變量置爲true,兩個都OK 了就更新Ui.
面試官說這是最基本的,有沒有更好的方法。
CountDownLatch
方案一:使用CountDownLatch
看下這個類的說明:
* A synchronization aid that allows one or more threads to wait until
* a set of operations being performed in other threads completes.
意思是,這個類可以允許我們讓一個或者多個線程,等待其他線程的一系列操作。
什麼意思,就是這個類可以幫助我們實現,上面提到的功能。我們可以利用這個類讓線程進入等待狀態,當兩個線程都結束了任務,那麼喚醒線程去做一些事情!
/**
* 線程等待鎖 測試
*/
public void testWaitForAll(){
CountDownLatch countDownLatch = new CountDownLatch(2);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
countDownLatch.countDown();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//toast 不能在子線程彈 爲什麼?
Toast.makeText(getActivity(), "thead1 down", Toast.LENGTH_SHORT).show();
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
countDownLatch.countDown();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//toast 不能在子線程彈 爲什麼?
Toast.makeText(getActivity(), "thead2 down", Toast.LENGTH_SHORT).show();
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//toast 不能在子線程彈 爲什麼?
Toast.makeText(getActivity(), "all work is work", Toast.LENGTH_SHORT).show();
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.shutdown();
}
CountDownLatch countDownLatch = new CountDownLatch(2);
構造方法裏面需要穿一個參數,表示我需要等待幾個鎖。
然後我們調用countDownLatch.await();
他就會讓當前的線程等待,等兩個任務都執行完了之後,就會喚醒當前線程。
當一個線程完成之後,調用countDownLatch.countDown();
就表示我已經完成了一個線程的任務,當所有的任務都執行,就會喚醒等待的那個線程。
上面的CountDownLatch 只能使用一次,不能重複使用,如果要重複使用,那麼使用下面的:
方案二: 使用CyclicBarrier
/**
* CyclicBarrier 每一個線程 都要等待 而 CountDownLatch 不是
*/
public void testCyclicBarrier(){
CyclicBarrier countDownLatch = new CyclicBarrier(2, new Runnable() {
@Override
public void run() {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//toast 不能在子線程彈 爲什麼?
Toast.makeText(getActivity(), "all work is work", Toast.LENGTH_SHORT).show();
}
});
}
});
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//toast 不能在子線程彈 爲什麼?
Toast.makeText(getActivity(), "thead1 down", Toast.LENGTH_SHORT).show();
}
});
try {
countDownLatch.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//toast 不能在子線程彈 爲什麼?
Toast.makeText(getActivity(), "thead2 down", Toast.LENGTH_SHORT).show();
}
});
try {
countDownLatch.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
CyclicBarrier 構造參數裏面有兩個,第一個表示我要等待幾個鎖,第二個表示當所有的任務都執行完之後,執行的一個Runnable.
如果有線程執行完成,那麼就調用await 方法。每調用一個await,CyclicBarrier 裏面當前未執行的任務數量就會減少1.
當時有一個缺點就是所有的線程都會一起等待。
如果需要重複使用CyclicBarrier 就countDownLatch.reset();
就可以了。
方案三:使用Future
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<?> futureOne = executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//toast 不能在子線程彈 爲什麼?
Toast.makeText(getActivity(), "thead1 down", Toast.LENGTH_SHORT).show();
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Future<?> futureTwo = executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//toast 不能在子線程彈 爲什麼?
Toast.makeText(getActivity(), "thead2 down", Toast.LENGTH_SHORT).show();
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
try {
futureOne.get();
futureTwo.get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//toast 不能在子線程彈 爲什麼?
Toast.makeText(getActivity(), "all work is work", Toast.LENGTH_SHORT).show();
}
});
}
});
Future get 方法會阻塞等待線程中任務的執行完成。
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
*/
V get() throws InterruptedException, ExecutionException;