JDK 1.5中的java.util.concurrnet包中引入了大量用来解决并发问题的组件,学习如何使用它们将会有助于你编写更加简单而且健壮、高效的代码。
CountDownLatch
被用来同步一个或者多个任务,强制它们等待由其他线程执行的任务完成后再继续执行
CountDownLatch在new对象的时候可以设置一个初始值,任何在这个对象上调用await()方法的线程均将阻塞,直至这个计数值到达0。其他任务在结束工作后,可以在该对象上调用countDown()来讲这个值减1
调用countDown()的线程并不会阻塞,只有在不为0的时候调用await()才会阻塞。CountDownLatch被设计为只能触发一次,如果你需要多次重复触发版的,则可以使用CycleBarrier
原理:使用AQS的state作为count,构建了一个共享锁,初始时设置共享次数为count,每调用一次countDown()则将共享次数减1,当state值减少为0的时候,CountDownLatch的await()操作直接返回
为了方便理解,可以举例如下:CountDownLatch就相当于一扇门,有N把一次性的钥匙可以打开它,如果你有钥匙的话(countDown()),就可以直接走(不阻塞);如果你没钥匙的话(await()),就只能等待(阻塞)。但是当这个门被打开N次后,门锁就自动失效了,没钥匙的人也可以顺利通过了(唤醒)。
下面是示例代码
public class CountDownLatchExample {
private static final int TASKSIZE = 4;
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(TASKSIZE);
List<Runnable> list = new ArrayList<>(TASKSIZE);
for(int i = 0; i < TASKSIZE; i++ ){
list.add(new Task("Task-" + i, latch));
}
ExecutorService service = Executors.newCachedThreadPool();
for(int i = 0; i < TASKSIZE; i++){
service.execute(list.get(i));
}
System.out.println("waiting for tasks finish");
latch.await();
System.out.println("all task finish, i'm running now");
}
static class Task implements Runnable{
private String name;
private CountDownLatch latch;
public Task(String name, CountDownLatch latch) {
this.name = name;
this.latch = latch;
}
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(1);
System.out.println(name + " finish the task");
} catch (InterruptedException e) {
//ignore
} finally {
latch.countDown();
}
}
}
}
运行结果如下:
waiting for tasks finish
Task-1 finish the task
Task-0 finish the task
Task-2 finish the task
Task-3 finish the task
all task finish, i'm running now
CyclicBarrier
循环栅栏,通过它可以实现让一组线程等待至某个状态之后,再全部同步执行。
之所以叫Cycle(循环),是因为当所有等待线程都被释放以后,CycleBarrier可以被重用。CycleBarrier支持一个可选的command(命令)操作,当最后一个线程到达barrier点的时候,执行command操作,然后释放所有的线程。
这个有点类似于Map-Reduce操作,先将任务分配(Map)出去单独执行,等待执行完成后,再进行一个Reduce操作,集合所有的结果。
为了方便理解,我们可以举例如下:CycleBarrier就相当于一扇门,这扇门拥有N把钥匙,任意一把钥匙都无法单独打开这扇门,只能等N把钥匙集齐了,才能把门打开,打开门之后,可以进行一个公共的操作(Command)。需要注意的是,在打开这扇门以后,这些钥匙可以复用。
下面是使用CycleBarrier模拟Map-Reduce操作进行数组求和,分为两个task进行计算:
public class CyclicBarrierTest {
private static final int WORK_SIZE = 2;
private static List<Task> lists = new ArrayList<>(WORK_SIZE);
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(WORK_SIZE, ()-> {
System.out.println("All task finish......");
int total = 0;
for (int i = 0; i < lists.size(); i++) {
total += lists.get(i).getResult();
}
System.out.println("the final result is " + total);
});
int[] data = new int[]{1,2,3,4,5,6,7,8};
int middle = data.length / 2; //假设每个task计算数组中一半的值
lists.add(new Task(barrier,data,0,middle));
lists.add(new Task(barrier,data,middle,data.length));
ExecutorService service = Executors.newCachedThreadPool();
for(int i = 0; i < lists.size(); i++){
service.execute(lists.get(i));
}
System.out.println("waiting task finish........");
}
static class Task implements Runnable{
private CyclicBarrier barrier;
private int start;
private int end;
private int[] data;
private int result;
public Task(CyclicBarrier barrier, int[] data, int start, int end) {
this.barrier = barrier;
this.start = start;
this.end = end;
this.data = data;
}
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(2);
for(int i = start;i<end;i++){
result += data[i];
}
System.out.println(Thread.currentThread().getName() + " task finish, result is " + result);
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
public int getResult() {
return result;
}
}
}
运行程序后得到结果如下:
waiting task finish........
pool-1-thread-2 task finish, result is 26
pool-1-thread-1 task finish, result is 10
All task finish......
the final result is 36
Semaphore
Semaphore 可以很轻松完成信号量控制,例如控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
Semaphore的特点类似于线程池一样,只不过其实一个“许可证”池,Semaphore在实现过程中分为公平和非公平两种信号量,其内部和ReentrantLock一样,均实现了FairSync和NonFairSync。
还是以开门为例,这扇门有N个钥匙,你可以通过acquire()操作获取一把钥匙,如果你获取钥匙成功,那么你就可以打开门离开;如果获取钥匙失败,你就必须等待。当然,在你成功获取钥匙打开门离开的时候,你也可以通过release()操作将钥匙放在门口,这样其他人就可以用这个钥匙开门了。