CountDownLatch看着字面意思根本不知道这个类的大概作用是什么。。通过查阅相关资料,得出的结论是这个类能够让当前线程在其他线程都执行完毕之后再执行。那么它是怎么做到这点的呐?其实按照之前的AQS,通过判断队列中的节点个数是否为0也可以做哦!那么他是否采用这种方式?
看一下CountDownLatch的方法,发现这块代码也是很少。而切中就是一个内部类Sync,而Sync就是继承了大名鼎鼎的AQS,看来确实跟AQS有着千丝万缕的关系。那么毫无疑问,这些方法都是最终都调用的Sync继而调用AQS。
这里用一下网上流传的例子:
public class MultiThread {
private static ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(10);
private static CountDownLatch latch;
private static final int ThreadNum = 10;
static {
for (int i = 0; i < 10; i++) {
blockingQueue.add("test" + i);
}
latch = new CountDownLatch(10);
}
/**
* 用blockQueue中的元素模拟文件分片
* @return
*/
public static String getFileSplit() {
return blockingQueue.poll();
}
static class myThread implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + "begin running...");
String m = getFileSplit();
System.out.println(m);
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
System.out.println(Thread.currentThread().getName() + "ended");
}
}
public static void main(String args[]){
System.out.println("主线程开始运行");
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i=0;i<ThreadNum;i++){
service.execute(new Thread(new myThread()));
}
service.shutdown();
System.out.println("线程已经全部运行");
System.out.println("等待所有线程运行结束");
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程退出");
}
}
确实实现了效果,但是每次进行一个latch.countDown();如果不进行这个操作,那么程序就会陷入等待。那么我们看看这块是怎么实现的。
在初始化函数中,我们看到传入了线程的数量,也就是Sync持有的锁的数量。这和之前分析的各种重入锁是一样的。只是现在一次性全赋值了。
在countDown方法中,我们看到还是调用的AQS的释放锁的函数。
getCount方法也是调用的AQS的方法unparkSuccessor,而unparkSuccessor的主要作用就是唤醒这里的主线程(这里逻辑比较复杂,之前已经分享过了吧,大概就是任务队列向头节点轮询,该跳过的跳过,该抛弃的抛弃,然后取出第一个进行激活,让其运行)。感觉本例中要明确的写死state的方式挺难的,因为现实中很多都是动态的。难道在现实问题中,就只能坐以待毙?还是countDownLatch只能解决局部可解的有限问题?我感觉是这么回事
总结:
CountDwonLatch都是调用的AQS的方法,类的本体方法很少。通过初始化的时候确定执行的线程任务来跑。能够在小范围的计算数据,比如对数据进行批量进行数据处理,然后再进行数据的结果的合并等。
CountDownLatch的使用一般为首先初始化AQS的锁数量(state),然后当一个线程完成任务的时候,都进行一次countDown对state进行减一操作。在最后所有线程做完之后,需要主线程进行运行时,需要进行await操作,在await方法中使用AQS的unparkSuccessor激活主线程。然后完成后边的计算。否则主线程将一直处于等待状态。