web应用程序并发测试_测试并发应用
web應用程序并發測試
本文是我們名為Java Concurrency Essentials的學院課程的一部分。
 在本課程中,您將深入探討并發的魔力。 將向您介紹并發和并發代碼的基礎知識,并學習諸如原子性,同步和線程安全性的概念。 在這里查看 ! 
目錄
1.簡介 2. SimpleBlockingQueue1.簡介
本文介紹了多線程應用程序的測試。 我們實現一個簡單的阻塞隊列,并測試其阻塞行為以及在壓力測試條件下的行為和性能。 最后,我們闡明了用于多線程類的單元測試的可用框架。
2. SimpleBlockingQueue
在本節中,我們將實現一個非常基本且簡單的阻塞Queue 。 這個隊列除了保留我們放入其中的元素并在調用get()時將它們還給他們外,什么也沒做。 在新元素可用之前, get()應該會阻塞。
很明顯, java.util.concurrent包已經提供了這樣的功能,并且不需要再次實現它,但是出于演示目的,我們在這里這樣做是為了展示如何測試這樣的類。
作為隊列的數據結構,我們從java.util包中選擇一個標準的LinkedList 。 此列表未同步,并且調用其get()方法不會阻塞。 因此,我們必須同步訪問列表,并且必須添加阻止功能。 后者可以通過一個簡單的while()循環來實現,當隊列為空時,該循環調用列表上的wait()方法。 如果隊列不為空,則返回第一個元素:
public class SimpleBlockingQueue<T> {private List<T> queue = new LinkedList<T>();public int getSize() {synchronized(queue) {return queue.size();}}public void put(T obj) {synchronized(queue) {queue.add(obj);queue.notify();}}public T get() throws InterruptedException {while(true) {synchronized(queue) {if(queue.isEmpty()) {queue.wait();} else {return queue.remove(0);}}}} }測試阻止操作
盡管此實現非常簡單,但是測試所有功能(尤其是阻止功能)并不是那么容易。 當我們只在空隊列上調用get()時,當前線程將被阻塞,直到另一個線程將新項插入隊列。 這意味著在單元測試中我們至少需要兩個不同的線程。 一個線程阻塞時,另一個線程等待特定的時間。 如果在此期間另一個線程不執行其他代碼,則可以假定阻止功能正在運行。 檢查阻塞線程沒有執行任何其他代碼的一種方法是,在引發異常或執行get()調用后的行時,添加一些設置的布爾標志:
private static class BlockingThread extends Thread {private SimpleBlockingQueue queue;private boolean wasInterrupted = false;private boolean reachedAfterGet = false;private boolean throwableThrown;public BlockingThread(SimpleBlockingQueue queue) {this.queue = queue;}public void run() {try {try {queue.get();} catch (InterruptedException e) {wasInterrupted = true;}reachedAfterGet = true;} catch (Throwable t) {throwableThrown = true;}}public boolean isWasInterrupted() {return wasInterrupted;}public boolean isReachedAfterGet() {return reachedAfterGet;}public boolean isThrowableThrown() {return throwableThrown;}}標志wasInterrupted指示阻塞線程是否被中斷,標志標志reachedAfterGet顯示執行了get之后的行,最后throwableThrown會告訴我們是否throwableThrown了任何類型的Throwable 。 使用這些標志的getter方法,我們現在可以編寫一個單元測試,該測試首先創建一個空隊列,啟動BlockingThread ,等待一段時間,然后將新元素插入隊列。
@Testpublic void testPutOnEmptyQueueBlocks() throws InterruptedException {final SimpleBlockingQueue queue = new SimpleBlockingQueue();BlockingThread blockingThread = new BlockingThread(queue);blockingThread.start();Thread.sleep(5000);assertThat(blockingThread.isReachedAfterGet(), is(false));assertThat(blockingThread.isWasInterrupted(), is(false));assertThat(blockingThread.isThrowableThrown(), is(false));queue.put(new Object());Thread.sleep(1000);assertThat(blockingThread.isReachedAfterGet(), is(true));assertThat(blockingThread.isWasInterrupted(), is(false));assertThat(blockingThread.isThrowableThrown(), is(false));blockingThread.join();}在插入之前,所有標志都應為false。 如果是這樣的話,我們把一個新的元素到隊列中,檢查標志reachedAfterGet設置為true。 所有其他標志仍應為false。 最后,我們可以join() blockingThread 。
測試正確性
先前的測試僅顯示了如何測試阻塞操作。 更有趣的是真正的多線程測試用例,其中我們有多個線程在隊列中添加元素,還有一堆工作線程從隊列中檢索這些值。 基本上,這意味著創建一些將新元素插入隊列的生產者線程,并設置一堆調用get()的工作線程。
但是,我們如何知道工作線程從隊列中獲得了與生產者線程之前插入的元素完全相同的元素呢? 一種可能的解決方案是讓第二個隊列在其中基于某些唯一ID(例如UUID)添加和刪除元素。 但是,由于我們處于多線程環境中,因此我們還必須同步對第二個隊列的訪問,并且某些唯一ID的創建也將強制進行某種同步。
更好的解決方案是一些無需任何額外同步即可實現的數學方法。 最簡單的方法是使用整數值作為隊列元素,這些值是從線程本地隨機生成器中檢索的。 特殊類ThreadLocalRandom為每個線程管理一個隨機生成器; 因此,我們沒有任何同步開銷。 生產者線程計算由它們插入的元素的總和,而工作線程也計算其本地和。 最后,將所有生產者線程的總和與所有消費者線程的總和進行比較。 如果相同,則可以假定我們很有可能已檢索到之前插入的所有任務。
以下單元測試通過將使用者線程和生產者線程作為任務提交到固定線程池來實現這些想法:
@Testpublic void testParallelInsertionAndConsumption() throws InterruptedException, ExecutionException {final SimpleBlockingQueue<Integer> queue = new SimpleBlockingQueue<Integer>();ExecutorService threadPool = Executors.newFixedThreadPool(NUM_THREADS);final CountDownLatch latch = new CountDownLatch(NUM_THREADS);List<Future<Integer>> futuresPut = new ArrayList<Future<Integer>>();for (int i = 0; i < 3; i++) {Future<Integer> submit = threadPool.submit(new Callable<Integer>() {public Integer call() {int sum = 0;for (int i = 0; i < 1000; i++) {int nextInt = ThreadLocalRandom.current().nextInt(100);queue.put(nextInt);sum += nextInt;}latch.countDown();return sum;}});futuresPut.add(submit);}List<Future<Integer>> futuresGet = new ArrayList<Future<Integer>>();for (int i = 0; i < 3; i++) {Future<Integer> submit = threadPool.submit(new Callable<Integer>() {public Integer call() {int count = 0;try {for (int i = 0; i < 1000; i++) {Integer got = queue.get();count += got;}} catch (InterruptedException e) {}latch.countDown();return count;}});futuresGet.add(submit);}latch.await();int sumPut = 0;for (Future<Integer> future : futuresPut) {sumPut += future.get();}int sumGet = 0;for (Future<Integer> future : futuresGet) {sumGet += future.get();}assertThat(sumPut, is(sumGet));}我們使用CountDownLatch來等待所有線程完成。 最后,我們可以計算所有提交和檢索的整數的總和,并斷言兩者相等。
很難預測執行不同線程的順序。 它取決于許多動態因素,例如操作系統處理的中斷以及調度程序如何選擇下一個要執行的線程。 為了實現更多的上下文切換,可以調用Thread.yield()方法。 這為調度程序提供了一個提示,即當前線程愿意讓CPU支持其他線程。 如javadoc所述,這只是一個提示,即JVM可以完全忽略該提示并進一步執行當前線程。 但是出于測試目的,可以使用此方法引入更多上下文切換,從而引發競爭條件等。
測試性能
除了類的正確行為之外,另一方面是它的性能。 在許多實際應用中,性能是一項重要要求,因此必須進行測試。
我們可以利用ExecutorService設置不同數量的線程。 每個線程都應該將一個元素插入到我們的隊列中,然后再從中獲取它。 在外部循環中,我們增加線程數以查看線程數如何影響吞吐量。
@Testpublic void testPerformance() throws InterruptedException {for (int numThreads = 1; numThreads < THREADS_MAX; numThreads++) {long startMillis = System.currentTimeMillis();final SimpleBlockingQueue<Integer> queue = new SimpleBlockingQueue<Integer>();ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);for (int i = 0; i < numThreads; i++) {threadPool.submit(new Runnable() {public void run() {for (long i = 0; i < ITERATIONS; i++) {int nextInt = ThreadLocalRandom.current().nextInt(100);try {queue.put(nextInt);nextInt = queue.get();} catch (InterruptedException e) {e.printStackTrace();}}}});}threadPool.shutdown();threadPool.awaitTermination(5, TimeUnit.MINUTES);long totalMillis = System.currentTimeMillis() - startMillis;double throughput = (double)(numThreads * ITERATIONS * 2) / (double) totalMillis;System.out.println(String.format("%s with %d threads: %dms (throughput: %.1f ops/s)", LinkedBlockingQueue.class.getSimpleName(), numThreads, totalMillis, throughput));}}為了了解我們的簡單隊列實現的性能,我們可以將其與JDK中的實現進行比較。 一種候選方法是LinkedBlockingQueue 。 它的兩個方法put()和take()工作方式與我們的實現類似,期望LinkedBlockingQueue有選擇地受限制,因此必須跟蹤所插入元素的數量,并在隊列已滿時讓當前線程進入Hibernate狀態。 此功能需要額外的簿記和插入操作檢查。 另一方面,JDK實現不使用同步塊,并且已經通過繁瑣的性能測量來實現。
當我們使用LinkedBlockingQueue實現與上述相同的測試用例時,兩個測試用例均獲得以下輸出:
圖1
該圖清楚地表明,對于LinkedBlockingQueue實現,吞吐率(即,每個時間單位執行的操作數)幾乎是其兩倍。 但是它也表明,只有一個線程,兩種實現每秒執行大約相同數量的操作。 添加更多線程可提高吞吐量,盡管曲線很快會針對其飽和值收斂。 添加更多線程不會再提高應用程序的性能。
3.測試框架
您可以查看可用的測試框架,而不是編寫自己的框架來為應用程序實現多線程測試用例。 本節重點介紹其中的兩個:JMock和Grobo Utils。
JMock
為了進行壓力測試,模擬框架JMock提供了Blitzer類。 該類實現的功能與我們在“測試正確性”部分中所做的類似,因為它在內部設置了一個ThreadPool ,執行某些特定操作的任務提交給該ThreadPool 。 您提供要執行的任務/動作的數量以及構造函數的線程數量:
Blitzer blitzer = new Blitzer(25000, 6);此實例具有方法blitz() ,您只需向其提供Runnable接口的實現:
@Testpublic void stressTest() throws InterruptedException {final SimpleBlockingQueue<Integer> queue = new SimpleBlockingQueue<Integer>();blitzer.blitz(new Runnable() {public void run() {try {queue.put(42);queue.get();} catch (InterruptedException e) {e.printStackTrace();}}});assertThat(queue.getSize(), is(0));}因此,與使用ExecutorService相比, Blitzer類使壓力測試的實現更加簡單。
Grobo Utils
Grobo Utils是一個框架,為測試多線程應用程序提供支持。 這篇文章描述了該框架背后的思想。
與前面的示例類似,我們擁有類MultiThreadedTestRunner ,該類在內部構造線程池并以給定數量的Runnable實現作為單獨的線程執行。 Runnable實例必須實現一個稱為TestRunnable的特殊接口。 值得一提的是,它的唯一方法runTest()會引發異常。 這樣,線程內拋出的異常會影響測試結果。 當我們使用普通的ExecutorService時,情況并非如此。 這里的任務必須實現Runnable并且它的唯一方法run()不會引發任何異常。 這些任務中引發的異常將被吞沒,并且不會破壞測試。
構建完MultiThreadedTestRunner之后,我們可以調用其runTestRunnables()方法并提供在測試失敗之前應等待的毫秒數。 最后, assertThat()調用將驗證隊列再次為空,因為所有測試線程都已刪除了先前添加的元素。
public class SimpleBlockingQueueGroboUtilTest {private static class MyTestRunnable extends TestRunnable {private SimpleBlockingQueue<Integer> queue;public MyTestRunnable(SimpleBlockingQueue<Integer> queue) {this.queue = queue;}@Overridepublic void runTest() throws Throwable {for (int i = 0; i < 1000000; i++) {this.queue.put(42);this.queue.get();}}}@Testpublic void stressTest() throws Throwable {SimpleBlockingQueue<Integer> queue = new SimpleBlockingQueue<Integer>();TestRunnable[] testRunnables = new TestRunnable[6];for (int i = 0; i < testRunnables.length; i++) {testRunnables[i] = new MyTestRunnable(queue);}MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(testRunnables);mttr.runTestRunnables(2 * 60 * 1000);assertThat(queue.getSize(), is(0));} }翻譯自: https://www.javacodegeeks.com/2015/09/testing-concurrent-applications.html
web應用程序并發測試
總結
以上是生活随笔為你收集整理的web应用程序并发测试_测试并发应用的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 修复linux系统的方法(修复linux
 - 下一篇: 电视安卓助手下载(电视安卓助手)