1. 程式人生 > >Java多執行緒-併發工具類(二)等待多執行緒完成的CountDownLatch

Java多執行緒-併發工具類(二)等待多執行緒完成的CountDownLatch

參考:

https://www.jianshu.com/p/1716ce690637
http://ifeve.com/talk-concurrency-countdownlatch/

CountDownLatch是什麼

CountDownLatch也叫閉鎖,在JDK1.5被引入,允許一個或多個執行緒等待其他執行緒完成操作後再執行。

CountDownLatch內部會維護一個初始值為執行緒數量的計數器,主執行緒執行await方法,如果計數器大於0,則阻塞等待。當一個執行緒完成任務後,計數器值減1。當計數器為0時,表示所有的執行緒已經完成任務,等待的主執行緒被喚醒繼續執行。

應用場景

應用程式的主執行緒希望在負責啟動框架服務的執行緒已經完成之後再執行。在這個需求中,要實現主執行緒等待所有執行緒完成的操作,最簡單的做法是使用join。但在JDK1.5之後的併發包中提供的CountDownLatch也可以實現join的這個功能,並且比join的功能更多。

例子1:

public class StartUpCheckService {
	private SimpleDateFormat sdf = new SimpleDateFormat("[YYYY-MM-dd HH:mm:ss:SSS] ");
	private void println(String msg) {
		System.out.println(sdf.format(new Date()) + msg);
	}
	
	/**
	 * 所有服務的基類,具體實現在execute方法實現
	 */
	class Service implements Runnable {
	    private CountDownLatch latch;

	    public Service(CountDownLatch latch) {
	        this.latch = latch;
	    }

	    @Override
	    public void run() {
	        try {
	            execute();
	        } finally {
	            if (latch != null)
	                latch.countDown();
	        }
	    }
	    public void execute() {}
	}
	
	/**
	 * 服務具體實現類.工作2秒
	 */
	class HealthCheckService extends Service {

	    public HealthCheckService(CountDownLatch latch) {
	        super(latch);
	    }

	    @Override
	    public void execute() {
	        try {
	        	println("HealthCheckService work...");
	            TimeUnit.SECONDS.sleep(2);
	            println("HealthCheckService done...");
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	    }
	}

	/**
	 * 服務具體實現類.工作3秒
	 */
	class DatabaseCheckService extends Service {

	    public DatabaseCheckService(CountDownLatch latch) {
	        super(latch);
	    }

	    @Override
	    public void execute() {
	        try {
	        	println("DatabaseCheckService work...");
	            TimeUnit.SECONDS.sleep(3);
	            println("DatabaseCheckService done...");
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	    }
	}
	
	/**
	 * 應用啟動類,使用執行緒池執行每個服務的任務。負責初始化閉鎖,然後等待,直到所有服務都被檢測完。
	 */
	class Application {
	    private CountDownLatch latch;
	    public void startUp() throws Exception {
	        latch = new CountDownLatch(2);
	        List<Service> services = new ArrayList<>();
	        services.add(new DatabaseCheckService(latch));
	        services.add(new HealthCheckService(latch));
	        ExecutorService executor = Executors.newFixedThreadPool(services.size());
	        for (Service service : services) {
	            executor.execute(service);
	        }
	        latch.await();
	        println("all service is start up");
	        executor.shutdown();
	    }
	}

	public static void main(String arg[]) {
		try {
			new StartUpCheckService().new Application().startUp();;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

輸出:


例子2:

public class Test {
	public static void main(String[] args) {
		final CountDownLatch latch = new CountDownLatch(2);

		new Thread() {
			public void run() {
				try {
					System.out.println("子執行緒" + Thread.currentThread().getName() + "正在執行");
					Thread.sleep(3000);
					System.out.println("子執行緒" + Thread.currentThread().getName() + "執行完畢");
					latch.countDown();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			};
		}.start();

		new Thread() {
			public void run() {
				try {
					System.out.println("子執行緒" + Thread.currentThread().getName() + "正在執行");
					Thread.sleep(3000);
					System.out.println("子執行緒" + Thread.currentThread().getName() + "執行完畢");
					latch.countDown();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			};
		}.start();

		try {
			System.out.println("等待2個子執行緒執行完畢...");
			latch.await();
			System.out.println("2個子執行緒已經執行完畢");
			System.out.println("繼續執行主執行緒");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

輸出:

子執行緒Thread-0正在執行
等待2個子執行緒執行完畢...
子執行緒Thread-1正在執行
子執行緒Thread-1執行完畢
子執行緒Thread-0執行完畢
2個子執行緒已經執行完畢
繼續執行主執行緒

CyclicBarrier和CountDownLatch的區別

  1. CountDownLatch的計數器只能使用一次。而CyclicBarrier的計數器可以使用reset() 方法重置。所以CyclicBarrier能處理更為複雜的業務場景,比如如果計算髮生錯誤,可以重置計數器,並讓執行緒們重新執行一次。
  2. CyclicBarrier還提供其他有用的方法,比如getNumberWaiting方法可以獲得CyclicBarrier阻塞的執行緒數量。isBroken方法用來知道阻塞的執行緒是否被中斷。
  3. CountDownLatch和CyclicBarrier都能夠實現執行緒之間的等待,只不過它們側重點不同:CountDownLatch一般用於某個執行緒A等待若干個其他執行緒執行完任務之後,它才執行;而CyclicBarrier一般用於一組執行緒互相等待至某個狀態,然後這一組執行緒再同時執行;