有時候要測試一下某個功能的并發(fā)能力,又不要想借助于其他測試工具,索性就自己寫簡單的demo模擬一個并發(fā)請求就最方便了。如果熟悉jemter的測試某接口的并發(fā)能力其實更專業(yè),此處只是自己折騰著玩。
CountDownLatch和CyclicBarrier是jdk concurrent包下非常有用的兩個并發(fā)工具類,它們提供了一種控制并發(fā)流程的手段。其實查看源碼它們都是在內(nèi)部維護了一個計數(shù)器控制流程的
CountDownLatch:一個或者多個線程,等待其他多個線程完成某件事情之后才能執(zhí)行;
CyclicBarrier:多個線程互相等待,直到到達同一個同步點,再繼續(xù)一起執(zhí)行。
CountDownLatch和CyclicBarrier的區(qū)別
CountDownLatch的計數(shù)器,線程完成一個記錄一個,計數(shù)器是遞減??計數(shù)器,只能使用一次
CyclicBarrier的計數(shù)器 更像是一個閥門,需要所有線程都到達,閥門才能打開,然后繼續(xù)執(zhí)行,計數(shù)器是遞增??計數(shù)器提供reset功能,可以多次使用
另外Semaphore可以控同時訪問的線程個數(shù),通過 acquire() 獲取一個許可,如果沒有就等待,而 release() 釋放一個許可。
通常我們模擬并發(fā)請求,一般都是多開幾個線程,發(fā)起請求就好了。但是方式,一般會存在啟動的先后順序了,算不得真正的同時并發(fā)!怎么樣才能做到真正的同時并發(fā)呢?是本文想說的點,java中提供了閉鎖 CountDownLatch, CyclicBarrier 剛好就用來做這種事就最合適了。
下面分別使用CountDownLatch和CyclicBarrier來模擬并發(fā)的請求
CountDownLatch模擬
package com.test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.concurrent.CountDownLatch; public class LatchTest { public static void main(String[] args) throws InterruptedException { Runnable taskTemp = new Runnable() { // 注意,此處是非線程安全的,留坑 private int iCounter; @Override public void run() { for(int i = 0; i < 10; i++) { // 發(fā)起請求 // HttpClientOp.doGet("https://www.baidu.com/"); iCounter++; System.out.println(System.nanoTime() + " [" + Thread.currentThread().getName() + "] iCounter = " + iCounter); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }; LatchTest latchTest = new LatchTest(); latchTest.startTaskAllInOnce(5, taskTemp); } public long startTaskAllInOnce(int threadNums, final Runnable task) throws InterruptedException { final CountDownLatch startGate = new CountDownLatch(1); final CountDownLatch endGate = new CountDownLatch(threadNums); for(int i = 0; i < threadNums; i++) { Thread t = new Thread() { public void run() { try { // 使線程在此等待,當開始門打開時,一起涌入門中 startGate.await(); try { task.run(); } finally { // 將結束門減1,減到0時,就可以開啟結束門了 endGate.countDown(); } } catch (InterruptedException ie) { ie.printStackTrace(); } } }; t.start(); } long startTime = System.nanoTime(); System.out.println(startTime + " [" + Thread.currentThread() + "] All thread is ready, concurrent going..."); // 因開啟門只需一個開關,所以立馬就開啟開始門 startGate.countDown(); // 等等結束門開啟 endGate.await(); long endTime = System.nanoTime(); System.out.println(endTime + " [" + Thread.currentThread() + "] All thread is completed."); return endTime - startTime; } }
執(zhí)行結果

CyclicBarrier模擬
// 與 閉鎖 結構一致 public class LatchTest { public static void main(String[] args) throws InterruptedException { Runnable taskTemp = new Runnable() { private int iCounter; @Override public void run() { // 發(fā)起請求 // HttpClientOp.doGet("https://www.baidu.com/"); iCounter++; System.out.println(System.nanoTime() + " [" + Thread.currentThread().getName() + "] iCounter = " + iCounter); } }; LatchTest latchTest = new LatchTest(); // latchTest.startTaskAllInOnce(5, taskTemp); latchTest.startNThreadsByBarrier(5, taskTemp); } public void startNThreadsByBarrier(int threadNums, Runnable finishTask) throws InterruptedException { // 設置柵欄解除時的動作,比如初始化某些值 CyclicBarrier barrier = new CyclicBarrier(threadNums, finishTask); // 啟動 n 個線程,與柵欄閥值一致,即當線程準備數(shù)達到要求時,柵欄剛好開啟,從而達到統(tǒng)一控制效果 for (int i = 0; i < threadNums; i++) { Thread.sleep(100); new Thread(new CounterTask(barrier)).start(); } System.out.println(Thread.currentThread().getName() + " out over..."); } } class CounterTask implements Runnable { // 傳入柵欄,一般考慮更優(yōu)雅方式 private CyclicBarrier barrier; public CounterTask(final CyclicBarrier barrier) { this.barrier = barrier; } public void run() { System.out.println(Thread.currentThread().getName() + " - " + System.currentTimeMillis() + " is ready..."); try { // 設置柵欄,使在此等待,到達位置的線程達到要求即可開啟大門 barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " - " + System.currentTimeMillis() + " started..."); } }
執(zhí)行結果

并發(fā)請求操作流程示意圖如下:

此處設置了一道門,以保證所有線程可以同時生效。但是,此處的同時啟動,也只是語言層面的東西,也并非絕對的同時并發(fā)。具體的調用還要依賴于CPU個數(shù),線程數(shù)及操作系統(tǒng)的線程調度功能等,不過咱們也無需糾結于這些了,重點在于理解原理!
畢竟測試并發(fā) 還得用專業(yè)的工具 jmeter 還是很方便的.
在這里給大家提供一個學習交流的平臺,Java技術交流┟?810309655
具有1-5工作經(jīng)驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加群。
在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內(nèi)進修、跳槽拿高薪的可以加群。
如果沒有工作經(jīng)驗,但基礎非常扎實,對java工作機制,常用設計思想,常用java開發(fā)框架掌握熟練的可以加群。
________________________________________________________________________________________________
加Java架構師進階交流群獲取Java工程化、高性能及分布式、高性能、深入淺出。高架構。
性能調優(yōu)、Spring,MyBatis,Netty源碼分析和大數(shù)據(jù)等多個知識點高級進階干貨的直播免費學習權限
都是大牛帶飛?讓你少走很多的彎路的?群號是:?810309655對了?小白勿進?最好是有開發(fā)經(jīng)驗
注:加群要求
1、具有工作經(jīng)驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。
2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內(nèi)進修、跳槽拿高薪的可以加。
3、如果沒有工作經(jīng)驗,但基礎非常扎實,對java工作機制,常用設計思想,常用java開發(fā)框架掌握熟練的,可以加。
4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統(tǒng)化,很難在技術領域繼續(xù)突破的可以加。
5.阿里Java高級大牛直播講解知識點,分享知識,多年工作經(jīng)驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!