java concurrent 探祕

lostinai發表於2013-06-04

編寫多執行緒的程式一直都是一件比較麻煩的事情,要考慮很多事情,處理不好還會出很多意想不到的麻煩。加上現在很多開發者接觸到的專案都是打著企業級旗號的B/S專案,大多數人都很少涉及多執行緒,這又為本文的主角增加了一份神祕感。

 

講到Java多執行緒,大多數人腦海中跳出來的是Thread、Runnable、synchronized……這些是最基本的東西,雖然已經足夠強大,但想要用好還真不容易。從JDK 1.5開始,增加了java.util.concurrent包,它的引入大大簡化了多執行緒程式的開發(要感謝一下大牛Doug Lee)。

 

java.util.concurrent包分成了三個部分,分別是java.util.concurrent、java.util.concurrent.atomic和java.util.concurrent.lock。內容涵蓋了併發集合類、執行緒池機制、同步互斥機制、執行緒安全的變數更新工具類、鎖等等常用工具。

 

為了便於理解,本文使用一個例子來做說明,交代一下它的場景:

假設要對一套10個節點組成的環境進行檢查,這個環境有兩個入口點,通過節點間的依賴關係可以遍歷到整個環境。依賴關係可以構成一張有向圖,可能存在環。為了提高檢查的效率,考慮使用多執行緒。

 

1、Executors

通過這個類能夠獲得多種執行緒池的例項,例如可以呼叫newSingleThreadExecutor()獲得單執行緒的ExecutorService,呼叫newFixedThreadPool()獲得固定大小執行緒池的ExecutorService。拿到ExecutorService可以做的事情就比較多了,最簡單的是用它來執行Runnable物件,也可以執行一些實現了Callable<T>的物件。用Thread的start()方法沒有返回值,如果該執行緒執行的方法有返回值那用ExecutorService就再好不過了,可以選擇submit()、invokeAll()或者invokeAny(),根據具體情況選擇合適的方法即可。

Java程式碼  收藏程式碼
  1. package service;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5. import java.util.concurrent.ExecutionException;  
  6. import java.util.concurrent.ExecutorService;  
  7. import java.util.concurrent.Executors;  
  8. import java.util.concurrent.Future;  
  9. import java.util.concurrent.TimeUnit;  
  10.   
  11. /** 
  12.  * 執行緒池服務類 
  13.  *  
  14.  * @author DigitalSonic 
  15.  */  
  16. public class ThreadPoolService {  
  17.     /** 
  18.      * 預設執行緒池大小 
  19.      */  
  20.     public static final int  DEFAULT_POOL_SIZE    = 5;  
  21.   
  22.     /** 
  23.      * 預設一個任務的超時時間,單位為毫秒 
  24.      */  
  25.     public static final long DEFAULT_TASK_TIMEOUT = 1000;  
  26.   
  27.     private int              poolSize             = DEFAULT_POOL_SIZE;  
  28.     private ExecutorService  executorService;  
  29.   
  30.     /** 
  31.      * 根據給定大小建立執行緒池 
  32.      */  
  33.     public ThreadPoolService(int poolSize) {  
  34.         setPoolSize(poolSize);  
  35.     }  
  36.   
  37.     /** 
  38.      * 使用執行緒池中的執行緒來執行任務 
  39.      */  
  40.     public void execute(Runnable task) {  
  41.         executorService.execute(task);  
  42.     }  
  43.   
  44.     /** 
  45.      * 線上程池中執行所有給定的任務並取回執行結果,使用預設超時時間 
  46.      *  
  47.      * @see #invokeAll(List, long) 
  48.      */  
  49.     public List<Node> invokeAll(List<ValidationTask> tasks) {  
  50.         return invokeAll(tasks, DEFAULT_TASK_TIMEOUT * tasks.size());  
  51.     }  
  52.   
  53.     /** 
  54.      * 線上程池中執行所有給定的任務並取回執行結果 
  55.      *  
  56.      * @param timeout 以毫秒為單位的超時時間,小於0表示不設定超時 
  57.      * @see java.util.concurrent.ExecutorService#invokeAll(java.util.Collection) 
  58.      */  
  59.     public List<Node> invokeAll(List<ValidationTask> tasks, long timeout) {  
  60.         List<Node> nodes = new ArrayList<Node>(tasks.size());  
  61.         try {  
  62.             List<Future<Node>> futures = null;  
  63.             if (timeout < 0) {  
  64.                 futures = executorService.invokeAll(tasks);  
  65.             } else {  
  66.                 futures = executorService.invokeAll(tasks, timeout, TimeUnit.MILLISECONDS);  
  67.             }  
  68.             for (Future<Node> future : futures) {  
  69.                 try {  
  70.                     nodes.add(future.get());  
  71.                 } catch (ExecutionException e) {  
  72.                     e.printStackTrace();  
  73.                 }  
  74.             }  
  75.         } catch (InterruptedException e) {  
  76.             e.printStackTrace();  
  77.         }  
  78.         return nodes;  
  79.     }  
  80.   
  81.     /** 
  82.      * 關閉當前ExecutorService 
  83.      *  
  84.      * @param timeout 以毫秒為單位的超時時間 
  85.      */  
  86.     public void destoryExecutorService(long timeout) {  
  87.         if (executorService != null && !executorService.isShutdown()) {  
  88.             try {  
  89.                 executorService.awaitTermination(timeout, TimeUnit.MILLISECONDS);  
  90.             } catch (InterruptedException e) {  
  91.                 e.printStackTrace();  
  92.             }  
  93.             executorService.shutdown();  
  94.         }  
  95.     }  
  96.   
  97.     /** 
  98.      * 關閉當前ExecutorService,隨後根據poolSize建立新的ExecutorService 
  99.      */  
  100.     public void createExecutorService() {  
  101.         destoryExecutorService(1000);  
  102.         executorService = Executors.newFixedThreadPool(poolSize);  
  103.     }  
  104.   
  105.     /** 
  106.      * 調整執行緒池大小 
  107.      * @see #createExecutorService() 
  108.      */  
  109.     public void setPoolSize(int poolSize) {  
  110.         this.poolSize = poolSize;  
  111.         createExecutorService();  
  112.     }  
  113. }  
 

這裡要額外說明一下invokeAll()和invokeAny()方法。前者會執行給定的所有Callable<T>物件,等所有任務完成後返回一個包含了執行結果的List<Future<T>>,每個Future.isDone()都是true,可以用Future.get()拿到結果;後者只要完成了列表中的任意一個任務就立刻返回,返回值就是執行結果。

還有一個比較詭異的地方
本程式碼是在JDK 1.6下編譯測試的,如果在JDK1.5下測試,很可能在invokeAll和invokeAny的地方出錯。明明ValidationTask實現了Callable<Node>,可是它死活不認,型別不匹配,這時可以將引數宣告由List<ValidationTask>改為List<Callable<Node>>。
造成這個問題的主要原因是兩個版本中invokeAll和invokeAny的方法簽名不同,1.6裡是invokeAll(Collection<? extends Callable<T>> tasks),而1.5裡是invokeAll(Collection<Callable<T>>tasks)。網上也有人遇到類似的問題(invokeAll() is not willing to acept a Collection<Callable<T>>)。

 

和其他資源一樣,執行緒池在使用完畢後也需要釋放,用shutdown()方法可以關閉執行緒池,如果當時池裡還有沒有被執行的任務,它會等待任務執行完畢,在等待期間試圖進入執行緒池的任務將被拒絕。也可以用shutdownNow()來關閉執行緒池,它會立刻關閉執行緒池,沒有執行的任務作為返回值返回。

 

2、Lock

多執行緒程式設計中常常要鎖定某個物件,之前會用synchronized來實現,現在又多了另一種選擇,那就是java.util.concurrent.locks。通過Lock能夠實現更靈活的鎖定機制,它還提供了很多synchronized所沒有的功能,例如嘗試獲得鎖(tryLock())。

 

使用Lock時需要自己獲得鎖並在使用後手動釋放,這一點與synchronized有所不同,所以通常Lock的使用方式是這樣的:

Java程式碼  收藏程式碼
  1. Lock l = ...;   
  2. l.lock();  
  3. try {  
  4.     // 執行操作  
  5. finally {  
  6.     l.unlock();  
  7. }  

 

java.util.concurrent.locks中提供了幾個Lock介面的實現類,比較常用的應該是ReentrantLock。以下範例中使用了ReentrantLock進行節點鎖定:

Java程式碼  收藏程式碼
  1. package service;  
  2.   
  3. import java.util.concurrent.locks.Lock;  
  4. import java.util.concurrent.locks.ReentrantLock;  
  5.   
  6. /** 
  7.  * 節點類 
  8.  *  
  9.  * @author DigitalSonic 
  10.  */  
  11. public class Node {  
  12.     private String name;  
  13.     private String wsdl;  
  14.     private String result = "PASS";  
  15.     private String[] dependencies = new String[] {};  
  16.     private Lock lock = new ReentrantLock();  
  17.     /** 
  18.      * 預設構造方法 
  19.      */  
  20.     public Node() {  
  21.     }  
  22.       
  23.     /** 
  24.      * 構造節點物件,設定名稱及WSDL 
  25.      */  
  26.     public Node(String name, String wsdl) {  
  27.         this.name = name;  
  28.         this.wsdl = wsdl;  
  29.     }  
  30.   
  31.     /** 
  32.      * 返回包含節點名稱、WSDL以及驗證結果的字串 
  33.      */  
  34.     @Override  
  35.     public String toString() {  
  36.         String toString = "Node: " + name + " WSDL: " + wsdl + " Result: " + result;  
  37.         return toString;  
  38.     }  
  39.       
  40.     // Getter & Setter  
  41.     public String getName() {  
  42.         return name;  
  43.     }  
  44.   
  45.     public void setName(String name) {  
  46.         this.name = name;  
  47.     }  
  48.   
  49.     public String getWsdl() {  
  50.         return wsdl;  
  51.     }  
  52.   
  53.     public void setWsdl(String wsdl) {  
  54.         this.wsdl = wsdl;  
  55.     }  
  56.   
  57.     public String getResult() {  
  58.         return result;  
  59.     }  
  60.   
  61.     public void setResult(String result) {  
  62.         this.result = result;  
  63.     }  
  64.   
  65.     public String[] getDependencies() {  
  66.         return dependencies;  
  67.     }  
  68.   
  69.     public void setDependencies(String[] dependencies) {  
  70.         this.dependencies = dependencies;  
  71.     }  
  72.   
  73.     public Lock getLock() {  
  74.         return lock;  
  75.     }  
  76.   
  77. }  
Java程式碼  收藏程式碼
  1. package service;  
  2.   
  3. import java.util.concurrent.Callable;  
  4. import java.util.concurrent.locks.Lock;  
  5. import java.util.logging.Logger;  
  6.   
  7. import service.mock.MockNodeValidator;  
  8.   
  9. /** 
  10.  * 執行驗證的任務類 
  11.  *  
  12.  * @author DigitalSonic 
  13.  */  
  14. public class ValidationTask implements Callable<Node> {  
  15.     private static Logger logger = Logger.getLogger("ValidationTask");  
  16.   
  17.     private String        wsdl;  
  18.   
  19.     /** 
  20.      * 構造方法,傳入節點的WSDL 
  21.      */  
  22.     public ValidationTask(String wsdl) {  
  23.         this.wsdl = wsdl;  
  24.     }  
  25.   
  26.     /** 
  27.      * 執行鍼對某個節點的驗證<br/> 
  28.      * 如果正有別的執行緒在執行同一節點的驗證則等待其結果,不重複執行驗證 
  29.      */  
  30.     @Override  
  31.     public Node call() throws Exception {  
  32.         Node node = ValidationService.NODE_MAP.get(wsdl);  
  33.         Lock lock = null;  
  34.         logger.info("開始驗證節點:" + wsdl);  
  35.         if (node != null) {  
  36.             lock = node.getLock();  
  37.             if (lock.tryLock()) {  
  38.                 // 當前沒有其他執行緒驗證該節點  
  39.                 logger.info("當前沒有其他執行緒驗證節點" + node.getName() + "[" + wsdl + "]");  
  40.                 try {  
  41.                     Node result = MockNodeValidator.validateNode(wsdl);  
  42.                     mergeNode(result, node);  
  43.                 } finally {  
  44.                     lock.unlock();  
  45.                 }  
  46.             } else {  
  47.                 // 當前有別的執行緒正在驗證該節點,等待結果  
  48.                 logger.info("當前有別的執行緒正在驗證節點" + node.getName() + "[" + wsdl + "],等待結果");  
  49.                 lock.lock();  
  50.                 lock.unlock();  
  51.             }  
  52.         } else {  
  53.             // 從未進行過驗證,這種情況應該只出現在系統啟動初期  
  54.             // 這時是在做初始化,不應該有衝突發生  
  55.             logger.info("首次驗證節點:" + wsdl);  
  56.             node = MockNodeValidator.validateNode(wsdl);  
  57.             ValidationService.NODE_MAP.put(wsdl, node);  
  58.         }  
  59.         logger.info("節點" + node.getName() + "[" + wsdl + "]驗證結束,驗證結果:" + node.getResult());  
  60.         return node;  
  61.     }  
  62.   
  63.     /** 
  64.      * 將src的內容合併進dest節點中,不進行深度拷貝 
  65.      */  
  66.     private Node mergeNode(Node src, Node dest) {  
  67.         dest.setName(src.getName());  
  68.         dest.setWsdl(src.getWsdl());  
  69.         dest.setDependencies(src.getDependencies());  
  70.         dest.setResult(src.getResult());  
  71.         return dest;  
  72.     }  
  73. }  
  

請注意ValidationTask的call()方法,這裡會先檢查節點是否被鎖定,如果被鎖定則表示當前有另一個執行緒正在驗證該節點,那就不用重複進行驗證。第50行和第51行,那到鎖後立即釋放,這裡只是為了等待驗證結束。

 

講到Lock,就不能不講Conditon,前者代替了synchronized,而後者則代替了Object物件上的wait()、notify()和notifyAll()方法(Condition中提供了await()、signal()和signalAll()方法),當滿足執行條件前掛起執行緒。Condition是與Lock結合使用的,通過Lock.newCondition()方法能夠建立與Lock繫結的Condition例項。JDK的JavaDoc中有一個例子能夠很好地說明Condition的用途及用法:

Java程式碼  收藏程式碼
  1. class BoundedBuffer {  
  2.   final Lock lock = new ReentrantLock();  
  3.   final Condition notFull  = lock.newCondition();   
  4.   final Condition notEmpty = lock.newCondition();   
  5.   
  6.   final Object[] items = new Object[100];  
  7.   int putptr, takeptr, count;  
  8.   
  9.   public void put(Object x) throws InterruptedException {  
  10.     lock.lock();  
  11.     try {  
  12.       while (count == items.length)   
  13.         notFull.await();  
  14.       items[putptr] = x;   
  15.       if (++putptr == items.length) putptr = 0;  
  16.       ++count;  
  17.       notEmpty.signal();  
  18.     } finally {  
  19.       lock.unlock();  
  20.     }  
  21.   }  
  22.   
  23.   public Object take() throws InterruptedException {  
  24.     lock.lock();  
  25.     try {  
  26.       while (count == 0)   
  27.         notEmpty.await();  
  28.       Object x = items[takeptr];   
  29.       if (++takeptr == items.length) takeptr = 0;  
  30.       --count;  
  31.       notFull.signal();  
  32.       return x;  
  33.     } finally {  
  34.       lock.unlock();  
  35.     }  
  36.   }   
  37. }  

說到這裡,讓我解釋一下之前的例子裡為什麼沒有選擇Condition來等待驗證結束。await()方法在呼叫時當前執行緒先要獲得對應的鎖,既然我都拿到鎖了,那也就是說驗證已經結束了。。。

 

3、併發集合類

集合類是大家程式設計時經常要使用的東西,ArrayList、HashMap什麼的,java.util包中的集合類有的是執行緒安全的,有的則不是,在編寫多執行緒的程式時使用執行緒安全的類能省去很多麻煩,但這些類的效能如何呢?java.util.concurrent包中提供了幾個併發結合類,例如ConcurrentHashMap、ConcurrentLinkedQueue和CopyOnWriteArrayList等等,根據不同的使用場景,開發者可以用它們替換java.util包中的相應集合類。

 

CopyOnWriteArrayList是ArrayList的一個變體,比較適合用在讀取比較頻繁、修改較少的情況下,因為每次修改都要複製整個底層陣列。ConcurrentHashMap中為Map介面增加了一些方法(例如putIfAbsenct()),同時做了些優化,總之灰常之好用,下面的程式碼中使用ConcurrentHashMap來作為全域性節點表,完全無需考慮併發問題。ValidationService中只是宣告(第17行),具體的使用是在上面的ValidationTask中。

Java程式碼  收藏程式碼
  1. package service;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5. import java.util.Map;  
  6. import java.util.concurrent.ConcurrentHashMap;  
  7.   
  8. /** 
  9.  * 執行驗證的服務類 
  10.  *  
  11.  * @author DigitalSonic 
  12.  */  
  13. public class ValidationService {  
  14.     /** 
  15.      * 全域性節點表 
  16.      */  
  17.     public static final Map<String, Node> NODE_MAP = new ConcurrentHashMap<String, Node>();  
  18.   
  19.     private ThreadPoolService threadPoolService;  
  20.       
  21.     public ValidationService(ThreadPoolService threadPoolService) {  
  22.         this.threadPoolService = threadPoolService;  
  23.     }  
  24.   
  25.     /** 
  26.      * 給出一個入口節點的WSDL,通過廣度遍歷的方式驗證與其相關的各個節點 
  27.      *  
  28.      * @param wsdl 入口節點WSDL 
  29.      */  
  30.     public void validate(List<String> wsdl) {  
  31.         List<String> visitedNodes = new ArrayList<String>();  
  32.         List<String> nextRoundNodes = new ArrayList<String>();  
  33.   
  34.         nextRoundNodes.addAll(wsdl);  
  35.         while (nextRoundNodes.size() > 0) {  
  36.             List<ValidationTask> tasks = getTasks(nextRoundNodes);  
  37.             List<Node> nodes = threadPoolService.invokeAll(tasks);  
  38.   
  39.             visitedNodes.addAll(nextRoundNodes);  
  40.             nextRoundNodes.clear();  
  41.             getNextRoundNodes(nodes, visitedNodes, nextRoundNodes);  
  42.         }  
  43.     }  
  44.   
  45.     private List<String> getNextRoundNodes(List<Node> nodes,  
  46.             List<String> visitedNodes, List<String> nextRoundNodes) {  
  47.         for (Node node : nodes) {  
  48.             for (String wsdl : node.getDependencies()) {  
  49.                 if (!visitedNodes.contains(wsdl)) {  
  50.                     nextRoundNodes.add(wsdl);  
  51.                 }  
  52.             }  
  53.         }  
  54.         return nextRoundNodes;  
  55.     }  
  56.   
  57.     private List<ValidationTask> getTasks(List<String> nodes) {  
  58.         List<ValidationTask> tasks = new ArrayList<ValidationTask>(nodes.size());  
  59.         for (String wsdl : nodes) {  
  60.             tasks.add(new ValidationTask(wsdl));  
  61.         }  
  62.         return tasks;  
  63.     }  
  64. }  

 

4、AtomicInteger

對變數的讀寫操作都是原子操作(除了long或者double的變數),但像數值型別的++ --操作不是原子操作,像i++中包含了獲得i的原始值、加1、寫回i、返回原始值,在進行類似i++這樣的操作時如果不進行同步問題就大了。好在java.util.concurrent.atomic為我們提供了很多工具類,可以以原子方式更新變數。

 

以AtomicInteger為例,提供了代替++ --的getAndIncrement()、incrementAndGet()、getAndDecrement()和decrementAndGet()方法,還有加減給定值的方法、當前值等於預期值時更新的compareAndSet()方法。

 

下面的例子中用AtomicInteger儲存全域性驗證次數(第69行做了自增的操作),因為validateNode()方法會同時被多個執行緒呼叫,所以直接用int不同步是不行的,但用AtomicInteger在這種場合下就很合適。

Java程式碼  收藏程式碼
  1. package service.mock;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.HashMap;  
  5. import java.util.List;  
  6. import java.util.Map;  
  7. import java.util.concurrent.atomic.AtomicInteger;  
  8. import java.util.logging.Logger;  
  9.   
  10. import service.Node;  
  11.   
  12. /** 
  13.  * 模擬執行節點驗證的Mock類 
  14.  *  
  15.  * @author DigitalSonic 
  16.  */  
  17. public class MockNodeValidator {  
  18.     public static final List<Node>         ENTRIES  = new ArrayList<Node>();  
  19.     private static final Map<String, Node> NODE_MAP = new HashMap<String, Node>();  
  20.   
  21.     private static AtomicInteger           count    = new AtomicInteger(0);  
  22.     private static Logger                  logger   = Logger.getLogger("MockNodeValidator");  
  23.   
  24.     /* 
  25.      * 構造模擬資料 
  26.      */  
  27.     static {  
  28.         Node node0 = new Node("NODE0""http://node0/check?wsdl"); //入口0  
  29.         Node node1 = new Node("NODE1""http://node1/check?wsdl");  
  30.         Node node2 = new Node("NODE2""http://node2/check?wsdl");  
  31.         Node node3 = new Node("NODE3""http://node3/check?wsdl");  
  32.         Node node4 = new Node("NODE4""http://node4/check?wsdl");  
  33.         Node node5 = new Node("NODE5""http://node5/check?wsdl");  
  34.         Node node6 = new Node("NODE6""http://node6/check?wsdl"); //入口1  
  35.         Node node7 = new Node("NODE7""http://node7/check?wsdl");  
  36.         Node node8 = new Node("NODE8""http://node8/check?wsdl");  
  37.         Node node9 = new Node("NODE9""http://node9/check?wsdl");  
  38.   
  39.         node0.setDependencies(new String[] { node1.getWsdl(), node2.getWsdl() });  
  40.         node1.setDependencies(new String[] { node3.getWsdl(), node4.getWsdl() });  
  41.         node2.setDependencies(new String[] { node5.getWsdl() });  
  42.         node6.setDependencies(new String[] { node7.getWsdl(), node8.getWsdl() });  
  43.         node7.setDependencies(new String[] { node5.getWsdl(), node9.getWsdl() });  
  44.         node8.setDependencies(new String[] { node3.getWsdl(), node4.getWsdl() });  
  45.   
  46.         node2.setResult("FAILED");  
  47.   
  48.         NODE_MAP.put(node0.getWsdl(), node0);  
  49.         NODE_MAP.put(node1.getWsdl(), node1);  
  50.         NODE_MAP.put(node2.getWsdl(), node2);  
  51.         NODE_MAP.put(node3.getWsdl(), node3);  
  52.         NODE_MAP.put(node4.getWsdl(), node4);  
  53.         NODE_MAP.put(node5.getWsdl(), node5);  
  54.         NODE_MAP.put(node6.getWsdl(), node6);  
  55.         NODE_MAP.put(node7.getWsdl(), node7);  
  56.         NODE_MAP.put(node8.getWsdl(), node8);  
  57.         NODE_MAP.put(node9.getWsdl(), node9);  
  58.   
  59.         ENTRIES.add(node0);  
  60.         ENTRIES.add(node6);  
  61.     }  
  62.   
  63.     /** 
  64.      * 模擬執行遠端驗證返回節點,每次呼叫等待500ms 
  65.      */  
  66.     public static Node validateNode(String wsdl) {  
  67.         Node node = cloneNode(NODE_MAP.get(wsdl));  
  68.         logger.info("驗證節點" + node.getName() + "[" + node.getWsdl() + "]");  
  69.         count.getAndIncrement();  
  70.         try {  
  71.             Thread.sleep(500);  
  72.         } catch (InterruptedException e) {  
  73.             e.printStackTrace();  
  74.         }  
  75.         return node;  
  76.     }  
  77.   
  78.     /** 
  79.      * 獲得計數器的值 
  80.      */  
  81.     public static int getCount() {  
  82.         return count.intValue();  
  83.     }  
  84.   
  85.     /** 
  86.      * 克隆一個新的Node物件(未執行深度克隆) 
  87.      */  
  88.     public static Node cloneNode(Node originalNode) {  
  89.         Node newNode = new Node();  
  90.   
  91.         newNode.setName(originalNode.getName());  
  92.         newNode.setWsdl(originalNode.getWsdl());  
  93.         newNode.setResult(originalNode.getResult());  
  94.         newNode.setDependencies(originalNode.getDependencies());  
  95.   
  96.         return newNode;  
  97.     }  
  98. }  

 

上述程式碼還有另一個功能,就是構造測試用的節點資料,一共10個節點,有2個入口點,通過這兩個點能夠遍歷整個系統。每次呼叫會模擬遠端訪問,等待500ms。環境間節點依賴如下:

環境依賴
Node0 [Node1, Node2]
Node1 [Node3, Node4]
Node2 [Node5]
Node6 [Node7, Node8]
Node7 [Node5, Node9]
Node8 [Node3, Node4]

 

5、CountDownLatch

CountDownLatch是一個一次性的同步輔助工具,允許一個或多個執行緒一直等待,直到計數器值變為0。它有一個構造方法,設定計數器初始值,即在await()結束等待前需要呼叫多少次countDown()方法。CountDownLatch的計數器不能重置,所以說它是“一次性”的,如果需要重置計數器,可以使用CyclicBarrier。在執行環境檢查的主類中,使用了CountDownLatch來等待所有驗證結束,在各個併發驗證的執行緒完成任務結束前都會呼叫countDown(),因為有3個併發的驗證,所以將計數器設定為3。

 

最後將所有這些類整合起來,執行環境檢查的主類如下。它會建立執行緒池服務和驗證服務,先做一次驗證(相當於是對系統做次初始化),隨後併發3個驗證請求。系統執行完畢會顯示實際執行的節點驗證次數和執行時間。如果是順序執行,驗證次數應該是13*4=52,但實際的驗證次數會少於這個數字(我這裡最近一次執行了33次驗證),因為如果同時有兩個執行緒要驗證同一節點時只會做一次驗證。關於時間,如果是順序執行,52次驗證每次等待500ms,那麼驗證所耗費的時間應該是26000ms,使用了多執行緒後的實際耗時遠小於該數字(最近一次執行耗時4031ms)。

Java程式碼  收藏程式碼
  1. package service.mock;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5. import java.util.concurrent.CountDownLatch;  
  6.   
  7. import service.Node;  
  8. import service.ThreadPoolService;  
  9. import service.ValidationService;  
  10.   
  11. /** 
  12.  * 模擬執行這個環境的驗證 
  13.  *  
  14.  * @author DigitalSonic 
  15.  */  
  16. public class ValidationStarter implements Runnable {  
  17.     private List<String>      entries;  
  18.     private ValidationService validationService;  
  19.     private CountDownLatch    signal;  
  20.   
  21.     public ValidationStarter(List<String> entries, ValidationService validationService,  
  22.             CountDownLatch signal) {  
  23.         this.entries = entries;  
  24.         this.validationService = validationService;  
  25.         this.signal = signal;  
  26.     }  
  27.   
  28.     /** 
  29.      * 執行緒池大小為10,初始化執行一次,隨後併發三個驗證 
  30.      */  
  31.     public static void main(String[] args) {  
  32.         ThreadPoolService threadPoolService = new ThreadPoolService(10);  
  33.         ValidationService validationService = new ValidationService(threadPoolService);  
  34.         List<String> entries = new ArrayList<String>();  
  35.         CountDownLatch signal = new CountDownLatch(3);  
  36.         long start;  
  37.         long stop;  
  38.   
  39.         for (Node node : MockNodeValidator.ENTRIES) {  
  40.             entries.add(node.getWsdl());  
  41.         }  
  42.   
  43.         start = System.currentTimeMillis();  
  44.   
  45.         validationService.validate(entries);  
  46.         threadPoolService.execute(new ValidationStarter(entries, validationService, signal));  
  47.         threadPoolService.execute(new ValidationStarter(entries, validationService, signal));  
  48.         threadPoolService.execute(new ValidationStarter(entries, validationService, signal));  
  49.   
  50.         try {  
  51.             signal.await();  
  52.         } catch (InterruptedException e) {  
  53.             e.printStackTrace();  
  54.         }  
  55.   
  56.         stop = System.currentTimeMillis();  
  57.         threadPoolService.destoryExecutorService(1000);  
  58.         System.out.println("實際執行驗證次數: " + MockNodeValidator.getCount());  
  59.         System.out.println("實際執行時間: " + (stop - start) + "ms");  
  60.     }  
  61.   
  62.     @Override  
  63.     public void run() {  
  64.         validationService.validate(entries);  
  65.         signal.countDown();  
  66.     }  
  67.   
  68. }  
  

=================================我是分割線==============================

 

本文沒有覆蓋java.util.concurrent中的所有內容,只是挑選一些比較常用的東西,想要獲得更多詳細資訊請閱讀JavaDoc。自打有了“輪子”理論,重複造大輪子的情況的確少了,但還是有人會做些小輪子,例如編寫多執行緒程式時用到的小工具(執行緒池、鎖等等),如果可以,請讓自己再“懶惰”一點吧~

轉自http://www.iteye.com/topic/363625

相關文章