【漫畫】JAVA並發編程之並發模擬工具

原創聲明:本文來源於公眾號【胖滾豬學編程】,轉載請註明出處。

上一節【漫畫】JAVA並發編程三大Bug源頭(可見性、原子性、有序性)我們聊了聊並發編程的三個bug源頭,這還沒開始進入並發世界,胖滾豬就遇到了難題。。

這個難題是所有初學者都會有的疑惑:沒法復現那些理論知識告訴我們的bug。但是實際操練很重要,那麼在本地開發環境,到底應該怎樣模擬並發呢?

_1

模擬並發工具大全

在本地模擬並發環境的方法有挺多的,比較熱門的有以下幾種,包括工具和代碼:

1、Postman:Http請求模擬工具,可以設置發起N個請求(但不推薦,並不專業)

2、Apache Bench(AB):Apache 服務器的一個web壓力測試工具,是一個命令行工具,可根據命令創建很多並發訪問線程,模擬多個訪問者同時對某一個URL地址進行訪問。總體來說,ab工具小巧簡單,上手學習較快,可以提供需要的基本性能指標;但是缺點就是沒有圖形化結果,不能監控。

3、Apache JMeter:Apache組織開發的基於Java的壓力測試工具。功能相比AB會更加強大,尤其是有GUI圖形化界面,這一點很爽。另外jmeter是一次完整的請求和返回;AB只是發出去請求,並不對返回做處理。如果你希望看到返回結果,那麼也應該選擇JMeter。

4、JAVA代碼:包括CountDownLatch、CyclicBarrier等

我們主要說一下如何通過jmeter和代碼來模擬並發環境,也推薦使用這兩種方式。

CountDownLatch(等待多線程完成)

原創聲明:本文來源於公眾號【胖滾豬學編程】,轉載請註明出處。

countDownLatch是在java1.5被引入,存在於java.util.cucurrent包下。

這個類能使一個線程等待其他線程各自執行完畢後再執行

它是通過一個計數器來實現的,計數器的初始值是線程的數量。每當一個線程執行完畢後,計數器的值就-1,當計數器的值為0時,表示所有線程都執行完畢,然後在閉鎖上等待的線程就可以恢復工作了。

如圖所示,初始值cnt=3,線程A執行了await方法所以被阻塞等待,T1\T2\T3線程通過調用countDown,讓計數器減一,直到cnt=0後,Thread A才恢復運行。換句話說就是,Thread A需要等待其他三個線程都執行完才能執行:
image

countDownLatch類中只提供了一個構造器:

//參數count為計數值
public CountDownLatch(int count) {  };
countDownLatch類中有三個方法是最重要的:

//調用await()方法的線程會被掛起,它會等待直到count值為0才繼續執行

public void await() throws InterruptedException { };
//和await()類似,只不過等待一定的時間後count值還沒變為0的話就會繼續執行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//將count值減1
public void countDown() { };

舉例:需求是處理一個Excel,Excel有很多行數據,為了效率,我們可以使用多線程處理,但是處理完所有行數據後,需要通知到主線程。如果沒有countDownLatch,那麼效果是這樣的,還沒處理完呢你就說我結束了:

image

如果在主線程上加上countDownLatch,並且調用await()方法,那麼主線程會被掛起,它會等待直到count值為0才繼續執行:

    static CountDownLatch countDownLatch = new CountDownLatch(100);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(() -> {
                doWork();
                countDownLatch.countDown();
            });
            thread.start();

        }
        countDownLatch.await();
        System.out.println("處理完整個excel的結束時間"+System.currentTimeMillis());
    }

    public static void doWork(){
        System.out.println("處理行數據,時間"+System.currentTimeMillis());
    }

_2

Thread thread = new Thread(() -> {
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    doWork();
});
thread.start();
countDownLatch.countDown();

_3

線程池

 ExecutorService executorService = new ThreadPoolExecutor(100,
         200, 60, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(20000), new ThreadFactory() {
     @Override
     public Thread newThread(Runnable r) {
         return new Thread(r);
     }
 });

CyclicBarrier(同步屏障)

原創聲明:本文來源於公眾號【胖滾豬學編程】,轉載請註明出處。

CyclicBarrier 的字面意思是可循環使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個線程到達屏障時,屏障才會開門,所有被屏障攔截的線程才會繼續幹活。

如下圖所示,一共4個線程A、B、C、D,它們到達屏障的順序可能各不相同。當A、B、C到達柵欄後,由於沒有滿足總數【4】的要求,所以會一直等待,當線程D到達後,柵欄才會放行。

image

CyclicBarrier構造方法

//其參數表示屏障攔截的線程數量
public CyclicBarrier(int parties)
//多了回調函數
public CyclicBarrier(int parties, Runnable barrierAction)

主要方法如下

//調用await方法的線程告訴CyclicBarrier自己已經到達同步點,然後當前線程被阻塞。直到parties個參與線程調用了await方法,阻塞線程才會執行。
public int await()
//帶超時時間參數
public int await(long timeout, TimeUnit unit)
//重置
public void reset()

根據這一特性,我們也可以用於並發模擬,如下代碼就是模擬了5個線程並發執行:

  private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

  public static void main(String[] args) throws InterruptedException {
      ExecutorService executorService = ExecutorServiceUtils.getExecutor();
      for (int i = 0; i < 20; i++) {
          int finalI = i;
          Thread.sleep(1000);
          executorService.submit(() -> {
              try {
                  doWork(finalI);
              } catch (Exception e) {
                  e.printStackTrace();
              }
          });
          //通過打印日誌 得出它具有自動重置功能
          log.info("Parties={} 等待中{}",cyclicBarrier.getParties(),cyclicBarrier.getNumberWaiting());
      }
      executorService.shutdown();
  }

  public static void doWork(Integer threadNum) throws Exception {
      Thread.sleep(1000);
      log.info("{} is ready", threadNum);
      cyclicBarrier.await();
      log.info("{} continue", threadNum);
  }

CountDownLatch 和 CyclicBarrier 的主要區別:

1、CountDownLatch 主要用來解決一個線程等待多個線程的場景,可以類比旅遊團團長要等待所有的遊客到齊才能去下一個景點;

而CyclicBarrier 是一組線程之間互相等待,更像是幾個驢友之間不離不棄。

2、CountDownLatch是減計數方式,而CyclicBarrier是加計數方式。

3、CountDownLatch 的計數器是不能循環利用的,也就是說一旦計數器減到 0,再有線程調用 await(),該線程會直接通過。

但CyclicBarrier 的計數器是可以循環利用的,而且具備自動重置的功能,一旦計數器減到 0 會自動重置到你設置的初始值。

4、除此之外,CyclicBarrier 還可以設置回調函數,可以說是功能豐富。而CountDownLatch不支持回調函數。

好了,現在再回到最開頭那段無法復現的代碼,我們用CyclicBarrier來改寫一下,你就可以看到出現0的情況了。

image

另外還要說一點:即使是真正的並發執行了,出問題依舊是小概率事件,本人親測運行了15次才出現0的結果。運氣不好可能運行100次還是不會看到。不必太過於糾結這些哦!

JMeter工具

1、下載好後解壓目錄,配置環境變量,運行jmeter.bat即可

2、添加一個線程組:

image

3、配置線程組相關屬性,主要是線程數量以及是否循環,這裡我們設置並發線程數是50,執行完每個線程後不需要循環:

image

4、添加http請求,比如我們要測試/test接口的請求:

image

配置http ip\端口\編碼格式等:

image

5、添加兩個監聽器、用於執行結束後查看圖形結果和查看結果數:

image

6、運行線程組,就可以輸出我們的結果了

image

好了,今天就到這裡了,又到了要跟大家說拜拜的時候了~

原創聲明:本文來源於公眾號【胖滾豬學編程】,轉載請註明出處。

本文轉載自公眾號【胖滾豬學編程】 用漫畫讓編程so easy and interesting!歡迎關注!形象來源於微信表情包【胖滾家族】喜歡可以下載哦~