1. 程式人生 > >CountDownLatch的工作原理以及例項

CountDownLatch的工作原理以及例項

        CountDownLatch、CyclicBarrier是多執行緒重要的類,本文主要進行對其主要原理的講解,並且通過舉例的形式,使得原理更加清晰,更易瞭解。

1.CountDownLatch工作原理

        CountDownLatch在多執行緒併發程式設計中充當一個計時器的功能,並且維護一個count的變數,並且其操作都是原子操作,該類主要通過countDown()和await()兩個方法實現功能的,首先通過建立CountDownLatch物件,並且傳入引數即為count初始值。如果一個執行緒呼叫了await()方法,那麼這個執行緒便進入阻塞狀態,並進入阻塞佇列。如果一個執行緒呼叫了countDown()方法,則會使count-1;當count的值為0時,這時候阻塞佇列中呼叫await()方法的執行緒便會逐個被喚醒,從而進入後續的操作。比如下面的例子就是有兩個操作,一個是讀操作一個是寫操作,現在規定必須進行完寫操作才能進行讀操作。所以當最開始呼叫讀操作時,需要用await()方法使其阻塞,當寫操作結束時,則需要使count等於0。因此count的初始值可以定為寫操作的記錄數,這樣便可以使得進行完寫操作,然後進行讀操作。

具體程式碼如下:

package concurrent;
import java.util.concurrent.CountDownLatch;
import java.util.*;
public class CountDownLatchDemo {
	
	private final static CountDownLatch cdl=new CountDownLatch(3);
	private final static Vector v=new Vector();
	
	private static class WriteThread extends Thread{
		private final String writeThreadName;
		private final int stopTime;
		private final String str;
		public WriteThread(String name,int time,String str)
		{
			this.writeThreadName=name;
			this.stopTime=time;
			this.str=str;
		}
		public void run()
		{
			System.out.println(writeThreadName+"開始寫入工作");
			try
			{
				Thread.sleep(stopTime);
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
			cdl.countDown();
			v.add(str);
			System.out.println(writeThreadName+"寫入內容為:"+str+"。寫入工作結束!");
		}
	}
	private static class ReadThread extends Thread{
		public void run()
		{
			System.out.println("讀操作之前必須先進行寫操作");
			try
			{
				cdl.await();//該執行緒進行等待,直到countDown減到0,然後逐個甦醒過來。
				//Thread.sleep(3000);
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
			for(int i=0;i<v.size();i++)
			{
				System.out.println("讀取第"+(i+1)+"條記錄內容為:"+v.get(i));
			}
			System.out.println("讀操作結束!");
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new ReadThread().start();
		new WriteThread("writeThread1",1000,"多執行緒知識點").start();
		new WriteThread("writeThread2",2000,"多執行緒CountDownLatch的知識點").start();
		new WriteThread("writeThread3",3000,"多執行緒中控制順序可以使用CountDownLatch").start();

	}

}

執行程式碼,結果如下:

讀操作之前必須先進行寫操作
writeThread1開始寫入工作
writeThread2開始寫入工作
writeThread3開始寫入工作
writeThread1寫入內容為:多執行緒知識點。寫入工作結束!
writeThread2寫入內容為:多執行緒CountDownLatch的知識點。寫入工作結束!
writeThread3寫入內容為:多執行緒中控制順序可以使用CountDownLatch。寫入工作結束!
讀取第1條記錄內容為:多執行緒知識點
讀取第2條記錄內容為:多執行緒CountDownLatch的知識點
讀取第3條記錄內容為:多執行緒中控制順序可以使用CountDownLatch
讀操作結束!

        從以上過程可以看出,可以使得先進行寫操作然後進行讀操作。

2.通過join實現CountDownLatch功能

        其實上述CountDownLatch這種功能可以通過Thread物件的join方法實現同樣的功能,只是這裡無須呼叫await()方法和countDown()方法,而是使用sleep()進行控制時間,然後將讀操作以及寫操作通過在主執行緒通過join()方法使其加入主執行緒,使其實現只有進行寫操作結束,才能進行讀操作。具體程式碼如下所示:

package concurrent;

import java.util.Vector;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class JoinDemo {
	private final static Vector v=new Vector();
	Lock lock=new ReentrantLock();
	final Condition condition=lock.newCondition();//建立condition物件
	
	private static class WriteThread extends Thread{
		private final String writeThreadName;
		private final int stopTime;
		private final String str;
		Lock lock=new ReentrantLock();
		final Condition condition=lock.newCondition();//建立condition物件
		public WriteThread(String name,int time,String str)
		{
			this.writeThreadName=name;
			this.stopTime=time;
			this.str=str;
		}
		public void run()
		{
			System.out.println(writeThreadName+"開始寫入工作");
			try
			{
				Thread.sleep(stopTime);
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
			
			v.add(str);
			System.out.println(writeThreadName+"寫入內容為:"+str+"。寫入工作結束!");
		}
	}
	private static class ReadThread extends Thread{
		Lock lock=new ReentrantLock();
		final Condition condition=lock.newCondition();//建立condition物件
		public void run()
		{
			System.out.println("讀操作之前必須先進行寫操作");
			try
			{
				Thread.sleep(10000);//該執行緒進行暫停,時間控制在寫操作結束才使執行緒甦醒過來。
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
			for(int i=0;i<v.size();i++)
			{
				System.out.println("讀取第"+(i+1)+"條記錄內容為:"+v.get(i));
			}
			System.out.println("讀操作結束!");
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		ReadThread readThread=new ReadThread();
		readThread.start();
		long start=System.currentTimeMillis();
		Thread[] write=new Thread[3];
		String[] str= {"多執行緒知識點","多執行緒CountDownLatch的知識點","多執行緒中控制順序可以使用CountDownLatch"};
		

		for(int i=0;i<3;i++)
		{
			Thread t1= new WriteThread("writeThread"+(i+1),1000*(i+1),str[i]);
	           
	        
	       
	        t1.start();
	        write[i]=t1;
		}
		
		try
		{
			readThread.join();
		}
		catch(InterruptedException e)
		{
			e.printStackTrace();
		}
		
		
		//等待執行緒結束
		for(Thread t:write)
		{
			try
			{
				t.join();
				
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}

		}
		//等待執行緒結束
		
		

	}

}

執行上述程式,可以得到如下結果:

讀操作之前必須先進行寫操作
writeThread1開始寫入工作
writeThread2開始寫入工作
writeThread3開始寫入工作
writeThread1寫入內容為:多執行緒知識點。寫入工作結束!
writeThread2寫入內容為:多執行緒CountDownLatch的知識點。寫入工作結束!
writeThread3寫入內容為:多執行緒中控制順序可以使用CountDownLatch。寫入工作結束!
讀取第1條記錄內容為:多執行緒知識點
讀取第2條記錄內容為:多執行緒CountDownLatch的知識點
讀取第3條記錄內容為:多執行緒中控制順序可以使用CountDownLatch
讀操作結束!

3.執行緒池問題

        通過上述的比較,可以通過join方法實現CountDownLatch的按順序執行執行緒的功能,但是CountDownLatch有join實現不了的情況,比如使用執行緒池時,執行緒池的執行緒不能直接使用,所以只能使用CountDownLatch實現按順序執行執行緒,而無法使用join()方法。具體程式碼如下:

package concurrent;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import java.util.*;
public class CountDownLatchDemo {
	
	private final static CountDownLatch cdl=new CountDownLatch(3);
	private final static Vector v=new Vector();
	private final static ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());//使用執行緒池
	
	private static class WriteThread extends Thread{
		private final String writeThreadName;
		private final int stopTime;
		private final String str;
		public WriteThread(String name,int time,String str)
		{
			this.writeThreadName=name;
			this.stopTime=time;
			this.str=str;
		}
		public void run()
		{
			System.out.println(writeThreadName+"開始寫入工作");
			try
			{
				Thread.sleep(stopTime);
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
			cdl.countDown();
			v.add(str);
			System.out.println(writeThreadName+"寫入內容為:"+str+"。寫入工作結束!");
		}
	}
	private static class ReadThread extends Thread{
		public void run()
		{
			System.out.println("讀操作之前必須先進行寫操作");
			try
			{
				cdl.await();//該執行緒進行等待,直到countDown減到0,然後逐個甦醒過來。
				//Thread.sleep(3000);
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
			for(int i=0;i<v.size();i++)
			{
				System.out.println("讀取第"+(i+1)+"條記錄內容為:"+v.get(i));
			}
			System.out.println("讀操作結束!");
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Thread read=new ReadThread();
		threadPool.execute(read);
		String[] str= {"多執行緒知識點","多執行緒CountDownLatch的知識點","多執行緒中控制順序可以使用CountDownLatch"};
		

		for(int i=0;i<3;i++)
		{
			Thread t1= new WriteThread("writeThread"+(i+1),1000*(i+1),str[i]);
	           
	        
	       
	        threadPool.execute(t1);
	        
		}
		
		//new WriteThread("writeThread1",1000,"多執行緒知識點").start();
		//new WriteThread("writeThread2",2000,"多執行緒CountDownLatch的知識點").start();
		//new WriteThread("writeThread3",3000,"多執行緒中控制順序可以使用CountDownLatch").start();

	}

}

執行如上程式,得到以下結果:

讀操作之前必須先進行寫操作
writeThread1開始寫入工作
writeThread2開始寫入工作
writeThread3開始寫入工作
writeThread1寫入內容為:多執行緒知識點。寫入工作結束!
writeThread2寫入內容為:多執行緒CountDownLatch的知識點。寫入工作結束!
writeThread3寫入內容為:多執行緒中控制順序可以使用CountDownLatch。寫入工作結束!
讀取第1條記錄內容為:多執行緒知識點
讀取第2條記錄內容為:多執行緒CountDownLatch的知識點
讀取第3條記錄內容為:多執行緒中控制順序可以使用CountDownLatch
讀操作結束!

4.總結

        CountDownLatch類主要是用來實現執行緒的按順序執行。主要通過count計數器來實現功能,CountDownLatch(int count)建構函式用於初始化同步計時器,只有當同步計時器為0,主執行緒才會向下執行。而實現按順序執行的兩個主要方法便是await()和countDown(),其中await()使暫時不想讓它執行的執行緒加入佇列進入阻塞狀態,而countDown()則是每當執行完一個可執行執行緒便會減1,直到count為0,那麼阻塞佇列的執行緒便會被喚醒。

        Thread物件的join方法可以實現相同的功能,但是特別地,當使用了執行緒池時,則join()方法便無法實現。但CountDownLatch依然可以實現功能。

            CountDownLatch類主要使用的場景有明顯的順序要求:比如只有等跑完步才能計算排名,只有等所有記錄都寫入才能進行統計工作等等,因此CountDownLatch完善的是某種邏輯上的功能,使得執行緒按照正確的邏輯進行。