Java中模擬並發請求,自然是很方便的,只要多開幾個線程,發起請求就好了。但是,這種請求,一般會存在啟動的先后順序了,算不得真正的同時並發!怎么樣才能做到真正的同時並發呢?是本文想說的點,java中提供了閉鎖 CountDownLatch, 剛好就用來做這種事就最合適了。
只需要:
1. 開啟n個線程,加一個閉鎖,開啟所有線程;
2. 待所有線程都准備好后,按下開啟按鈕,就可以真正的發起並發請求了。
1.CountDownLatch
CountDownLatch 是 java.util.concurrent 包下的一個同步輔助類,它能使一個或多個線程在其他的線程的一系列操作完成之前一直等待。
CountDownLatch
是一種通用的同步工具,可用於多種用途。
(1) 一個CountDownLatch
為一個計數的CountDownLatch用作一個簡單的開/關鎖存器,或者門:所有線程調用await
在門口等待,直到被調用countDown()
的線程打開。
(2) 一個CountDownLatch
初始化N可以用來做一個線程等待,直到N個線程完成某項操作,或某些動作已經完成N次
常用方法API
(1)await()方法
public void await() throws InterruptedException
線程阻塞,直到計數器為0,才會啟動。
(2)countDown()方法
public void countDown()
減少鎖存器的計數,如果計數達到零,釋放所有等待的線程。
(3)構造方法
public CountDownLatch(int count)
構造一個以給定計數 CountDownLatch
CountDownLatch。
2.使用CountDownLatch實現模擬多線程並發請求
2.1 方法1
測試線程類:
package org.jeecg.modules.test; import java.util.Random; import java.util.concurrent.CountDownLatch; /** * @Author lucky * @Date 2021/11/18 14:48 */ public class MyTestThread implements Runnable{ private final CountDownLatch startSignal; public MyTestThread(CountDownLatch startSignal) { super(); this.startSignal = startSignal; } @Override public void run() { try { startSignal.await(); //一直阻塞當前線程,直到計時器的值為0 } catch (InterruptedException e) { e.printStackTrace(); } doTask(); } private void doTask() { // TODO Auto-generated method stub long startTime = System.currentTimeMillis(); //1. 模擬調用Flep SDK請求操作(讓線程睡眠隨機毫秒數) Random random = new Random(); int timeduration=100+random.nextInt(300); // 返回給定返回的隨機秒數 try { Thread.sleep(timeduration); } catch (Exception e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + " ended at: " + endTime + ", cost: " + (endTime - startTime) + " ms."); } }
測試主函數類
package org.jeecg.modules.test; import java.util.concurrent.CountDownLatch; /** * @Author lucky * @Date 2021/11/18 15:04 */ public class MyMainTest { public void concurrentInit(int threadNum){ CountDownLatch start=new CountDownLatch(1);// 初始化計數器為1 for(int i=0;i<threadNum;i++){//模擬用戶設置的給定數量的線程 MyTestThread tt =new MyTestThread(start); Thread t = new Thread(tt); t.start(); }
Thread.sleep(1000);//等1s,讓所有子線程創建完畢,都進入run方法,執行await()方法 start.countDown();//計數器減1 所有線程釋放 同時跑 } public static void main(String[] args) { int threadNum=20; MyMainTest myMainTest=new MyMainTest(); myMainTest.concurrentInit(threadNum); } }
注意:這種方法有一個坑,由於主線程和子線程存在爭奪cpu時間片,若不讓主線程睡眠1s,則主線程和子線程會同時爭搶CPU時間片,導致start.countDown()這句代碼在所有子線程阻塞住之前執行,並發效果無法實現。
原因分析:Thread類下的start()和run()方法的區別
多線程原理:相當於玩游戲機,只有一個游戲機(cpu),可是有很多人要玩,於是,start是排隊!等CPU選中你就是輪到你,你就run(),當CPU的運行的時間片執行完,這個線程就繼續排隊,等待下一次的run()。
調用start()后,線程會被放到等待隊列,等待CPU調度,並不一定要馬上開始執行,只是將這個線程置於可動行狀態。然后通過JVM,線程Thread會調用run()方法,執行本線程的線程體。先調用start后調用run,這么麻煩,為了不直接調用run?就是為了實現多線程的優點,沒這個start不行。
1) start:用start方法來啟動線程,真正實現了多線程運行,這時無需等待run方法體代碼執行完畢而直接繼續執行下面的代碼。通過調用Thread類的start()方法來啟動一個線程,這時此線程處於就緒(可運行)狀態,並沒有運行,一旦得到cpu時間片,就開始執行run()方法,這里方法 run()稱為線程體,它包含了要執行的這個線程的內容,Run方法運行結束,此線程隨即終止。
2) run: run()方法只是類的一個普通方法而已,如果直接調用Run方法,程序中依然只有主線程這一個線程,其程序執行路徑還是只有一條,還是要順序執行,還是要等待run方法體執行完畢后才可繼續執行下面的代碼,這樣就沒有達到寫線程的目的。總結:調用start方法方可啟動線程,而run方法只是thread的一個普通方法調用,還是在主線程里執行。這兩個方法應該都比較熟悉,把需要並行處理的代碼放在run()方法中,start()方法啟動線程將自動調用 run()方法,這是由jvm的內存機制規定的。並且run()方法必須是public訪問權限,返回值類型為void。
記住:多線程就是分時利用CPU,宏觀上讓所有線程一起執行 ,也叫並發。
2.2 方法2(推薦)
測試主函數類
@RestController @RequestMapping("/test/current") @Slf4j public class TestConcurrentController { @PostMapping("/uploadFileInner") public void uploadFileInner(@RequestParam String contentId,@RequestParam String filePath,@RequestParam int threadNum){ log.info("--------------------------------------------------"); //01 創建指定threadNum個數的線程 final CountDownLatch latch =new CountDownLatch(threadNum); for (int i = 0; i <threadNum ; i++) { String fileFullPath=filePath+i+".doc"; MyConcurrentUploadThread uploadThread=new MyConcurrentUploadThread(latch, contentId, fileFullPath); Thread t=new Thread(uploadThread); t.start(); } } }
測試線程類:
@Slf4j public class MyConcurrentUploadThread implements Runnable { private final CountDownLatch latch; private final String contentId; private final String fileFullPath; public MyConcurrentUploadThread(CountDownLatch latch, String contentId, String fileFullPath) { this.latch = latch; this.contentId = contentId; this.fileFullPath = fileFullPath; } @Override public void run() { //01 當子線程爭搶到CPU時間片時,latch中計數減一,直至計數減至0,放開阻塞,所有線程一起跑 latch.countDown(); log.info(Thread.currentThread().getName()+",prepare at: "+System.currentTimeMillis()); try { //02 阻塞線程 latch.await(); doTask(); } catch (InterruptedException e) { e.printStackTrace(); } } private void doTask() { long startTime = System.currentTimeMillis(); //1. 模擬調用Flep SDK請求操作(讓線程睡眠隨機毫秒數) Random random = new Random(); int timeduration=100+random.nextInt(600); // 返回給定返回的隨機秒數 try { Thread.sleep(timeduration); } catch (Exception e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); log.info(Thread.currentThread().getName() + ", start at: "+startTime+", end at: " + endTime + ", cost: " + (endTime - startTime) + " ms."); } }
測試截圖:
參考文獻:https://blog.csdn.net/u010739551/article/details/79538425
https://blog.csdn.net/qq_45537574/article/details/111910615 (CountDownLatch+ThreadPoolExecutor線程池)
https://blog.csdn.net/linzhijia0612/article/details/44017715
https://blog.csdn.net/qq_38955098/article/details/109386827
https://blog.csdn.net/qq_43783527/article/details/114656839(推薦)
https://blog.csdn.net/leo187/article/details/98509472----最終參考