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完善的是某種邏輯上的功能,使得執行緒按照正確的邏輯進行。