J.U.C之CountDownLatch
一、CountDownLatch介绍
CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程等待。
用给定的计数初始化CountDownLatch。由于调用了CountDown方法,所以在当计数达到0之前,await方法会一直阻塞。之后,会释放所有等待的线程,await的所有后续调用都将立即返回。
CountDownLatch经典的做法是两种:
1. 作为开关:
l 初始化计数器为1,作为一个主子线程的开关。
l 初始化计数器为n,主线程等待n个子线程完成任务后再执行后续的工作
2. 拆分子任务
将一个问题分成 N 个部分,用执行每个部分并让锁存器倒计数的 Runnable 来描述每个部分,然后将所有 Runnable 加入到 Executor 队列。当所有的子部分完成后,协调线程就能够通过 await。
二、实例
开关的使用:
package com.mylearn.thread; import com.mylearn.util.DateUtil; import java.util.Date; import java.util.concurrent.CountDownLatch; /** * Created by IntelliJ IDEA. * User: yingkh * Date: 12-12-20 * Time: 上午10:59 * CopyRight:360buy * Descrption: CountDownLatch 类的使用 * To change this template use File | Settings | File Templates. */ public class CountDownLatchDemo { public static void main(String args[]) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(2); //保证子线程完毕后主线程继续工作 CountDownLatch countDownLatchMain = new CountDownLatch(1); //保证子线程同时开始工作 Worker worker1 = new Worker("zhangsna", 5000, countDownLatch,countDownLatchMain); Worker worker2 = new Worker("lisi", 8000, countDownLatch,countDownLatchMain); worker1.start(); worker2.start(); doSth(); countDownLatchMain.countDown();//在 countDownLatchMain计数器减1之前,子线程是阻塞的 countDownLatch.await();//等待所有工人完成工作,主线程才能继续工作 doSthElse(); System.out.println("all work complete at" + DateUtil.date2String(new Date(), DateUtil.DATE_FORMAT_1)); } private static void doSthElse() { try { System.out.println("主线程再休息一秒"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } } private static void doSth() { try { System.out.println("主线程休息一秒"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } } static class Worker extends Thread { String workerName; int workTime; CountDownLatch countDownLatch; CountDownLatch countDownLatchMain; public Worker(String workerName, int workTime, CountDownLatch countDownLatch, CountDownLatch countDownLatchMain) { this.workerName = workerName; this.workTime = workTime; this.countDownLatch = countDownLatch; this.countDownLatchMain = countDownLatchMain; } public void run() { try { countDownLatchMain.await(); } catch (InterruptedException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } System.out.println("Worker " + workerName + " do work begin at" + DateUtil.date2String(new Date(), DateUtil.DATE_FORMAT_1)); doWork(); //工作 System.out.println("Worker " + workerName + " do work complete at" + DateUtil.date2String(new Date(), DateUtil.DATE_FORMAT_1)); countDownLatch.countDown(); //完成工作,计数器减1 } private void doWork() { try { Thread.sleep(workTime); } catch (InterruptedException e) { e.printStackTrace(); } } } } |
结果:
主线程休息一秒 Worker zhangsna do work begin at2013-11-10 08:53:06 Worker lisi do work begin at2013-11-10 08:53:06 Worker zhangsna do work complete at2013-11-10 08:53:11 Worker lisi do work complete at2013-11-10 08:53:14 主线程再休息一秒 all work complete at2013-11-10 08:53:15 |
第二种用法:
package com.mylearn.thread; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by IntelliJ IDEA. * User: yingkh * Date: 13-11-10 * Time: 上午8:56 * CopyRight:360buy * Descrption: * CountDownLatch的第二种使用,通过 * To change this template use File | Settings | File Templates. */ public class CountDownLatch2Demo { public static void main(String args[]) { int n=10; CountDownLatch countDownLatch =new CountDownLatch(n); ExecutorService executorService = Executors.newCachedThreadPool(); for(int i=0;i<n;i++) { Woker woker =new Woker(i,countDownLatch); executorService.submit(woker); //调用不同的worker工作 } try { countDownLatch.await(); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } static class Woker implements Runnable{ int i; CountDownLatch countDownLatch; Woker(int i, CountDownLatch countDownLatch) { this.i = i; this.countDownLatch = countDownLatch; } public void run() { dowWork(i); //根据参数调用做不同的事 countDownLatch.countDown(); } private void dowWork(int i) { //模拟分页 int pageSize = 100; int begin = i* 100; int end = begin+99; System.out.println("worker is running between ["+ begin +"] with [" + end+"]."); } } } |
三、源码分析
Await:
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } |
acquireSharedInterruptibly:
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } |
tryAcquireShared:
public int tryAcquireShared(int acquires) { return getState() == 0? 1 : -1; // } |
countDownLatch的tryAcquireShared方法非常简单,只是判断计数是否为0;如果为0,线程不受影响,如果大于0,需要阻塞线程。doAcquireSharedInterruptibly是AQS公用的阻塞方法,在前几篇中分享过,此处略。
countDown:
public void countDown() { sync.releaseShared(1); } |
releaseShared
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { //返回true之后,唤醒阻塞队列 Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } |
tryReleaseShared
public boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { int c = getState(); if (c == 0) return false; int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; //只有减为0时会返回true } } } |
countDown操作很简单,仅仅是更新计数器,通过CAS,减一;如果减为0的时候唤醒阻塞队列