我们在开发过程中,会有这样的场景:
我有两个线程,一个线程去获取一个网络数据,我要等待两个线程回来之后,结合他们的数据去展示到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;