android 开发,我们会遇到一些并发场景,虽然不多,但还是有的。我们可以自己写代码解决并发问题,也可以借助java提供的工具类。接下来就介绍几个类及它们的使用场景。
CountDownLatch 是 java 1.5 时被引入的,它的作用是使一个线程等其他线程执行完毕后再执行,这么介绍听起来比较抽象,那举个具体的栗子:在android开发中,某个界面显示数据需要请求接口,但此时不是一个接口,而是两个,也就是说我们需要把两个接口返回的数据处理后再一起显示到UI界面上。可能会问:服务端为什么不把两个接口的数据放到一个接口里?理由有n个,比如一个接口是php,一个是java;比如两个接口牵涉的数据库不是一个部门的;再比如服务端哥们比较懒,为了方便...... 我们自己处理两个接口,一般是每个接口都加一个boolean值,在刷新UI前判断这两个boolean值是否都为true,满足条件后再刷新。此时我们就可以使用 CountDownLatch 来实现这个功能,举个简单的例子,两个子线程执行完后再执行主线程的操作。
private final static String TAG = "TestActivity";
private void testCountDownLatch() {
final CountDownLatch latch = new CountDownLatch(2);
Thread t1 = new Thread(){
public void run() {
try {
Log.e(TAG, "子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
Log.e(TAG, "子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread(){
public void run() {
try {
Log.e(TAG, "子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
Log.e(TAG, "子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
try {
t1.start();
t2.start();
Log.e(TAG, "等待2个子线程执行完毕...");
latch.await();
Log.e(TAG, "2个子线程已经执行完毕");
Log.e(TAG, "继续执行主线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
打印数据为:
17:17:03.457 18161-18161/TestActivity: 等待2个子线程执行完毕...
17:17:03.457 18161-18456/TestActivity: 子线程Thread-4正在执行
17:17:03.458 18161-18457/TestActivity: 子线程Thread-5正在执行
17:17:06.458 18161-18456/TestActivity: 子线程Thread-4执行完毕
17:17:06.459 18161-18457/TestActivity: 子线程Thread-5执行完毕
17:17:06.460 18161-18161/TestActivity: 2个子线程已经执行完毕
17:17:06.460 18161-18161/TestActivity: 继续执行主线程
从打印的日志也能看出来,两个子线程执行完后才执行UI线程,这个和上面举的例子一样。需要注意的是构造方法 new CountDownLatch(2) 限制是2,await() 方法是开启等待状态,countDown() 是标识当前线程执行完毕,可以放行了。
CyclicBarrier 用于多个线程计算数据,最后计算数据结果,它与 CountDownLatch 有点像,但 CountDownLatch 是一次性的,CyclicBarrier 是可以循环使用的,就像上面的例子,用 CyclicBarrier 也能实现。CyclicBarrier 的构造方法提供了一个形参 Runnable,当里面的子线程执行完了,就会执行当前 Runnable。
private void testCyclicBarrier() {
int N = 2;
Runnable runable = new Runnable() {
@Override
public void run() {
Log.e(TAG, "当前线程" + Thread.currentThread().getName() + " 所有线程写入完毕,继续处理其他任务...");
}
};
final CyclicBarrier barrier = new CyclicBarrier(N, runable);
Thread t1 = new Thread(){
public void run() {
try {
Log.e(TAG, "子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
Log.e(TAG, "子线程"+Thread.currentThread().getName()+"执行完毕");
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread(){
public void run() {
try {
Log.e(TAG, "子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
Log.e(TAG, "子线程"+Thread.currentThread().getName()+"执行完毕");
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
};
try {
t1.start();
t2.start();
Log.e(TAG, "等待2个子线程执行完毕...");
} catch (Exception e) {
e.printStackTrace();
}
}
在子线程中调用 testCyclicBarrier() 方法,打印日志为:
17:53:15.159 27735-27829/TestActivity: 等待2个子线程执行完毕...
17:53:15.160 27735-27831/TestActivity: 子线程Thread-3正在执行
17:53:15.160 27735-27830/TestActivity: 子线程Thread-2正在执行
17:53:18.160 27735-27831/TestActivity: 子线程Thread-3执行完毕
17:53:18.161 27735-27830/TestActivity: 子线程Thread-2执行完毕
17:53:18.161 27735-27830/TestActivity: 当前线程Thread-2 所有线程写入完毕,继续处理其他任务...
看到日志就可以发现,CountDownLatch 是在UI线程中执行,CyclicBarrier 则是在最后一个执行完毕的子线程中执行接下来的操作,如果我们要刷新UI,需要切换到UI线程。testCyclicBarrier() 中,我们可以在方法的末尾,把上面的代码赋值一遍,继续执行,这就是所谓的可循环利用。
Semaphore 的作用,更像是个管理器,举个栗子,本人农村长大,村头一片土地要浇地,这是二十户人的地,东西两头各有一水井,我们会先排号,按照排号顺序,一家浇完了另外一家继续浇地。有二十块地要浇,但由于是两口水井,同一时刻最多只能浇两块地。
private static void testSemaphore(){
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i< 20; i++){
new IrrigateThread("用户 " + i, semaphore).start();
}
}
static class IrrigateThread extends Thread{
private String name;
private Semaphore semaphore;
public IrrigateThread(String name, Semaphore semaphore){
this.name = name;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("IrrigateThread: 开始浇地 " + name +" " + getNowDate());
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("IrrigateThread: 浇地完毕 " + name +" " + getNowDate());
semaphore.release();
}
}
}
public static String getNowDate() {
Date currentTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
String dateString = formatter.format(currentTime);
return dateString;
}
打印日志为
IrrigateThread: 开始浇地 用户 1 2020-04-16 15:01:25:725
IrrigateThread: 开始浇地 用户 0 2020-04-16 15:01:25:725
IrrigateThread: 浇地完毕 用户 1 2020-04-16 15:01:26:751
IrrigateThread: 开始浇地 用户 2 2020-04-16 15:01:26:752
IrrigateThread: 浇地完毕 用户 0 2020-04-16 15:01:26:751
IrrigateThread: 开始浇地 用户 3 2020-04-16 15:01:26:754
IrrigateThread: 浇地完毕 用户 2 2020-04-16 15:01:27:752
IrrigateThread: 开始浇地 用户 4 2020-04-16 15:01:27:752
IrrigateThread: 浇地完毕 用户 3 2020-04-16 15:01:27:754
IrrigateThread: 开始浇地 用户 5 2020-04-16 15:01:27:754
IrrigateThread: 浇地完毕 用户 4 2020-04-16 15:01:28:752
IrrigateThread: 开始浇地 用户 6 2020-04-16 15:01:28:752
IrrigateThread: 浇地完毕 用户 5 2020-04-16 15:01:28:754
IrrigateThread: 开始浇地 用户 7 2020-04-16 15:01:28:754
IrrigateThread: 浇地完毕 用户 6 2020-04-16 15:01:29:754
IrrigateThread: 开始浇地 用户 8 2020-04-16 15:01:29:755
IrrigateThread: 浇地完毕 用户 7 2020-04-16 15:01:29:757
IrrigateThread: 开始浇地 用户 9 2020-04-16 15:01:29:757
IrrigateThread: 浇地完毕 用户 8 2020-04-16 15:01:30:755
IrrigateThread: 开始浇地 用户 10 2020-04-16 15:01:30:755
IrrigateThread: 浇地完毕 用户 9 2020-04-16 15:01:30:758
IrrigateThread: 开始浇地 用户 11 2020-04-16 15:01:30:758
IrrigateThread: 浇地完毕 用户 10 2020-04-16 15:01:31:755
IrrigateThread: 开始浇地 用户 12 2020-04-16 15:01:31:755
IrrigateThread: 浇地完毕 用户 11 2020-04-16 15:01:31:758
IrrigateThread: 开始浇地 用户 13 2020-04-16 15:01:31:758
IrrigateThread: 浇地完毕 用户 12 2020-04-16 15:01:32:755
IrrigateThread: 开始浇地 用户 14 2020-04-16 15:01:32:755
IrrigateThread: 浇地完毕 用户 13 2020-04-16 15:01:32:759
IrrigateThread: 开始浇地 用户 15 2020-04-16 15:01:32:759
IrrigateThread: 浇地完毕 用户 14 2020-04-16 15:01:33:756
IrrigateThread: 开始浇地 用户 16 2020-04-16 15:01:33:756
IrrigateThread: 浇地完毕 用户 15 2020-04-16 15:01:33:760
IrrigateThread: 开始浇地 用户 17 2020-04-16 15:01:33:760
IrrigateThread: 浇地完毕 用户 16 2020-04-16 15:01:34:756
IrrigateThread: 开始浇地 用户 18 2020-04-16 15:01:34:756
IrrigateThread: 浇地完毕 用户 17 2020-04-16 15:01:34:760
IrrigateThread: 开始浇地 用户 19 2020-04-16 15:01:34:760
IrrigateThread: 浇地完毕 用户 18 2020-04-16 15:01:35:757
IrrigateThread: 浇地完毕 用户 19 2020-04-16 15:01:35:760
我们可以看到,按照顺序,依次执行。
业务中有个场景,比如说某个页面要显示的数据有点特殊,有三个接口,哪个接口先获取到就显示哪个。如果直接这么做了,对手机流量和服务器资源是个浪费,接下来产品同学就定策略了,三个接口排个序,依次执行,间隔1秒。也就是说第一个请求后,如果1秒到了,数据没返回,那么调用第二个,依次类推。简单写个 demo
private static void test10A() {
final Semaphore semaphore = new Semaphore(1);
final AtomicInteger workComplete = new AtomicInteger(0);
final AtomicInteger taskCounter = new AtomicInteger(0);
System.out.println(" test10 first " + getNowDate());
int count = 3;
while (count > 0){
try {
semaphore.tryAcquire(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
}
if (workComplete.get() == 1) {
return;
}
taskCounter.incrementAndGet();
System.out.println(" test10 second " + " " + count-- + " " + getNowDate());
final int value = count;
new Thread(new Runnable() {
@Override
public void run() {
Ru r = new Ru(workComplete, taskCounter, semaphore);
try {
if(value == 2){
Thread.sleep(2500);
} else {
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
r.onSuccess();
}
}
}).start();
}
System.out.println(" test10 three " + getNowDate());
try {
semaphore.tryAcquire(1000L, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
}
System.out.println(" test10 three2 " + getNowDate());
if (workComplete.get() == 0) {
workComplete.set(1);
System.out.println(" test10 deliver " + getNowDate());
}
}
static class Ru {
AtomicInteger workComplete;//加载任务是否完成的标志位
AtomicInteger taskCounter;//任务计数器
Semaphore semaphore;
public Ru(AtomicInteger workComplete, AtomicInteger taskCounter, Semaphore semaphore){
this.workComplete = workComplete;
this.taskCounter = taskCounter;
this.semaphore = semaphore;
}
public void onFail() {
if (taskCounter.decrementAndGet() == 0) {
System.out.println(" test10 onFail " + getNowDate());
semaphore.release();
}
}
public void onSuccess(){
workComplete.set(1);
onFail();
}
}
配合 AtomicInteger 使用,进行拦截。打印日志为
test10 first 2020-04-16 16:24:03:256
test10 second 3 2020-04-16 16:24:03:280
test10 second 2 2020-04-16 16:24:04:282
test10 onFail 2020-04-16 16:24:05:782
明显是第二条线程的时间短,在1秒内返回了,所以第三条就不用执行了。 tryAcquire(long timeout, TimeUnit unit) 这个方法意思是在当前线程,阻塞timeout 后继续执行。