Java通過代碼模擬高并發可以以最快的方式發現我們系統中潛在的線程安全性問題,此處使用Semaphore(信號量)和 CountDownLatch(閉鎖)搭配ExecutorService(線程池)來進行模擬,主要介紹如下:
1、Semaphore
JDK 1.5之后會提供這個類
Semaphore是一種基于計數的信號量。它可以設定一個閾值,基于此,多個線程競爭獲取許可信號,做完自己的申請后歸還,超過閾值后,線程申請許可信號將會被阻塞。Semaphore可以用來構建一些對象池,資源池之類的,比如數據庫連接池,我們也可以創建計數為1的Semaphore,將其作為一種類似互斥鎖的機制,這也叫二元信號量,表示兩種互斥狀態。
2、CountDownLatch
JDK 1.5之后會提供這個類,
CountDownLatch這個類能夠使一個線程等待其他線程完成各自的工作后再執行。例如,應用程序的主線程希望在負責啟動框架服務的線程已經啟動所有的框架服務之后再執行。
CountDownLatch是通過一個計數器來實現的,計數器的初始值為線程的數量。每當一個線程完成了自己的任務后,計數器的值就會減1。當計數器值到達0時,它表示所有的線程已經完成了任務,然后在閉鎖上等待的線程就可以恢復執行任務。
如下圖:
以上兩個類可以搭配使用,達到模擬高并發的效果,以下使用代碼的形式進行舉例:
- package modules;
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Semaphore;
- public class CountExample {
- // 請求總數
- public static int clientTotal = 5000;
- // 同時并發執行的線程數
- public static int threadTotal = 200;
- public static int count = 0;
- public static void main(String[] args) throws Exception {
- ExecutorService executorService = Executors.newCachedThreadPool();
- //信號量,此處用于控制并發的線程數
- final Semaphore semaphore = new Semaphore(threadTotal);
- //閉鎖,可實現計數器遞減
- final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
- for (int i = 0; i < clientTotal ; i++) {
- executorService.execute(() -> {
- try {
- //執行此方法用于獲取執行許可,當總計未釋放的許可數不超過200時,
- //允許通行,否則線程阻塞等待,直到獲取到許可。
- semaphore.acquire();
- add();
- //釋放許可
- semaphore.release();
- } catch (Exception e) {
- //log.error("exception", e);
- e.printStackTrace();
- }
- //閉鎖減一
- countDownLatch.countDown();
- });
- }
- countDownLatch.await();//線程阻塞,直到閉鎖值為0時,阻塞才釋放,繼續往下執行
- executorService.shutdown();
- log.info("count:{}", count);
- }
- private static void add() {
- count++;
- }
- }
如上方法模擬5000次請求,同時最大200個并發操作,觀察最后的結果,發現每次的結果都有差別,和預期不符,得出結果部分如下:
- 22:18:26.449 [main] INFO modules.CountExample - count:4997
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:4995
- 22:18:26.449 [main] INFO modules.CountExample - count:4998
最后結論:add 方法 非線程安全
那如何保證add方法 線程安全,將add方法進行如下修改即可:
- private static void add() {
- count.incrementAndGet();
- }
執行結果如下:
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
最后結論:修改后 的 add 方法 線程安全