Java 中如何模拟真正的同时并发请求?
有時需要測試一下某個功能的并發性能,又不要想借助于其他工具,索性就自己的開發語言,來一個并發請求就最方便了。
java中模擬并發請求,自然是很方便的,只要多開幾個線程,發起請求就好了。但是,這種請求,一般會存在啟動的先后順序了,算不得真正的同時并發!怎么樣才能做到真正的同時并發呢?是本文想說的點,java中提供了閉鎖 CountDownLatch, 剛好就用來做這種事就最合適了。
只需要:
開啟n個線程,加一個閉鎖,開啟所有線程;
待所有線程都準備好后,按下開啟按鈕,就可以真正的發起并發請求了。
其執行效果如下圖所示:
httpClientOp 工具類,可以使用 成熟的工具包,也可以自己寫一個簡要的訪問方法,參考如下:
class?HttpClientOp?{public?static?String?doGet(String?httpurl)?{HttpURLConnection?connection?=?null;InputStream?is?=?null;BufferedReader?br?=?null;String?result?=?null;//?返回結果字符串try?{//?創建遠程url連接對象URL?url?=?new?URL(httpurl);//?通過遠程url連接對象打開一個連接,強轉成httpURLConnection類connection?=?(HttpURLConnection)?url.openConnection();//?設置連接方式:getconnection.setRequestMethod("GET");//?設置連接主機服務器的超時時間:15000毫秒connection.setConnectTimeout(15000);//?設置讀取遠程返回的數據時間:60000毫秒connection.setReadTimeout(60000);//?發送請求connection.connect();//?通過connection連接,獲取輸入流if?(connection.getResponseCode()?==?200)?{is?=?connection.getInputStream();//?封裝輸入流is,并指定字符集br?=?new?BufferedReader(new?InputStreamReader(is,?"UTF-8"));//?存放數據StringBuffer?sbf?=?new?StringBuffer();String?temp?=?null;while?((temp?=?br.readLine())?!=?null)?{sbf.append(temp);sbf.append("\r\n");}result?=?sbf.toString();}}?catch?(MalformedURLException?e)?{e.printStackTrace();}?catch?(IOException?e)?{e.printStackTrace();}?finally?{//?關閉資源if?(null?!=?br)?{try?{br.close();}?catch?(IOException?e)?{e.printStackTrace();}}if?(null?!=?is)?{try?{is.close();}?catch?(IOException?e)?{e.printStackTrace();}}connection.disconnect();//?關閉遠程連接}return?result;}public?static?String?doPost(String?httpUrl,?String?param)?{HttpURLConnection?connection?=?null;InputStream?is?=?null;OutputStream?os?=?null;BufferedReader?br?=?null;String?result?=?null;try?{URL?url?=?new?URL(httpUrl);//?通過遠程url連接對象打開連接connection?=?(HttpURLConnection)?url.openConnection();//?設置連接請求方式connection.setRequestMethod("POST");//?設置連接主機服務器超時時間:15000毫秒connection.setConnectTimeout(15000);//?設置讀取主機服務器返回數據超時時間:60000毫秒connection.setReadTimeout(60000);//?默認值為:false,當向遠程服務器傳送數據/寫數據時,需要設置為trueconnection.setDoOutput(true);//?默認值為:true,當前向遠程服務讀取數據時,設置為true,該參數可有可無connection.setDoInput(true);//?設置傳入參數的格式:請求參數應該是 name1=value1&name2=value2 的形式。connection.setRequestProperty("Content-Type",?"application/x-www-form-urlencoded");//?設置鑒權信息:Authorization: Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0connection.setRequestProperty("Authorization",?"Bearer?da3efcbf-0845-4fe3-8aba-ee040be542c0");//?通過連接對象獲取一個輸出流os?=?connection.getOutputStream();//?通過輸出流對象將參數寫出去/傳輸出去,它是通過字節數組寫出的os.write(param.getBytes());//?通過連接對象獲取一個輸入流,向遠程讀取if?(connection.getResponseCode()?==?200)?{is?=?connection.getInputStream();//?對輸入流對象進行包裝:charset根據工作項目組的要求來設置br?=?new?BufferedReader(new?InputStreamReader(is,?"UTF-8"));StringBuffer?sbf?=?new?StringBuffer();String?temp?=?null;//?循環遍歷一行一行讀取數據while?((temp?=?br.readLine())?!=?null)?{sbf.append(temp);sbf.append("\r\n");}result?=?sbf.toString();}}?catch?(MalformedURLException?e)?{e.printStackTrace();}?catch?(IOException?e)?{e.printStackTrace();}?finally?{//?關閉資源if?(null?!=?br)?{try?{br.close();}?catch?(IOException?e)?{e.printStackTrace();}}if?(null?!=?os)?{try?{os.close();}?catch?(IOException?e)?{e.printStackTrace();}}if?(null?!=?is)?{try?{is.close();}?catch?(IOException?e)?{e.printStackTrace();}}//?斷開與遠程地址url的連接connection.disconnect();}return?result;} }如上,就可以發起真正的并發請求了。
并發請求操作流程示意圖如下:
此處設置了一道門,以保證所有線程可以同時生效。但是,此處的同時啟動,也只是語言層面的東西,也并非絕對的同時并發。具體的調用還要依賴于CPU個數,線程數及操作系統的線程調度功能等,不過咱們也無需糾結于這些了,重點在于理解原理!
與 CountDownLatch 有類似功能的,還有個工具柵欄 CyclicBarrier, 也是提供一個等待所有線程到達某一點后,再一起開始某個動作,效果一致,不過柵欄的目的確實比較純粹,就是等待所有線程到達,而前面說的閉鎖 CountDownLatch 雖然實現的也是所有線程到達后再開始,但是他的觸發點其實是 最后那一個開關,所以側重點是不一樣的。
簡單看一下柵欄是如何實現真正同時并發呢?示例如下:
//?與?閉鎖?結構一致 public?class?LatchTest?{public?static?void?main(String[]?args)?throws?InterruptedException?{Runnable?taskTemp?=?new?Runnable()?{private?int?iCounter;@Overridepublic?void?run()?{//?發起請求 //??????????????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?個線程,與柵欄閥值一致,即當線程準備數達到要求時,柵欄剛好開啟,從而達到統一控制效果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?{//?傳入柵欄,一般考慮更優雅方式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...");} }其運行結果如下圖:
各有其應用場景吧,關鍵在于需求。就本文示例的需求來說,個人更愿意用閉鎖一點,因為更可控了。但是代碼卻是多了,所以看你喜歡吧!
總結
以上是生活随笔為你收集整理的Java 中如何模拟真正的同时并发请求?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot Redis 入门
- 下一篇: StringBuilder 为什么线程不