作者:京東保險(xiǎn) 張新磊
背景
在現(xiàn)代軟件測(cè)試的廣闊領(lǐng)域中,我們的工作不僅限于確保功能符合產(chǎn)品和業(yè)務(wù)需求的嚴(yán)格標(biāo)準(zhǔn)。隨著用戶對(duì)應(yīng)用性能的期望水漲船高,性能測(cè)試已成為衡量軟件質(zhì)量的關(guān)鍵指標(biāo)。特別是在服務(wù)端接口的性能測(cè)試中,我們面臨的挑戰(zhàn)不僅僅是處理單個(gè)請(qǐng)求的效率,更在于如何在多用戶同時(shí)訪問(wèn)時(shí)保持系統(tǒng)的穩(wěn)定性和響應(yīng)速度。并發(fā)編程和測(cè)試,作為性能測(cè)試的核心,對(duì)于評(píng)估系統(tǒng)在高負(fù)載情況下的表現(xiàn)、識(shí)別潛在的性能瓶頸、以及優(yōu)化資源配置具有至關(guān)重要的作用。
并發(fā)編程是一門(mén)藝術(shù),它要求開(kāi)發(fā)者在多線程或多進(jìn)程的環(huán)境中精心編排代碼,以實(shí)現(xiàn)資源的高效共享和任務(wù)的并行執(zhí)行。這不僅需要深厚的編程功底,更需要對(duì)并發(fā)模型、同步機(jī)制和線程安全性有深刻的認(rèn)識(shí)。而在測(cè)試領(lǐng)域,性能測(cè)試工程師必須精通如何構(gòu)建并發(fā)測(cè)試場(chǎng)景,運(yùn)用工具模擬真實(shí)的高并發(fā)環(huán)境,以及如何從測(cè)試結(jié)果中提煉出有價(jià)值的洞察,以指導(dǎo)性能的持續(xù)優(yōu)化。
本文將深入剖析并發(fā)編程的深層原理、面臨的挑戰(zhàn)以及采納的最佳實(shí)踐。同時(shí),我們將探討并發(fā)測(cè)試的策略、工具和技術(shù),并通過(guò)實(shí)際案例的分析,闡釋如何在軟件開(kāi)發(fā)生命周期中有效地整合并發(fā)測(cè)試,以及如何利用并發(fā)測(cè)試來(lái)顯著提升系統(tǒng)的性能和可靠性。
多線程基礎(chǔ)和作用
進(jìn)程與線程的區(qū)別
資源分配:進(jìn)程是資源分配的基本單位,線程是CPU調(diào)度和執(zhí)行的基本單位。
獨(dú)立性:進(jìn)程是獨(dú)立運(yùn)行的,而線程則依賴于進(jìn)程。
內(nèi)存分配:進(jìn)程有自己的內(nèi)存空間,線程共享進(jìn)程的內(nèi)存空間。
開(kāi)銷:線程的創(chuàng)建和切換開(kāi)銷小于進(jìn)程。
并發(fā)性:線程可以提高程序的并發(fā)性,因?yàn)樗鼈兛梢圆⑿袌?zhí)行。
Java中線程的創(chuàng)建方式
方式一.繼承Thread類
當(dāng)你創(chuàng)建一個(gè)繼承自Thread類的子類時(shí),你需要重寫(xiě)run方法,該方法包含了線程要執(zhí)行的代碼。然后,你可以通過(guò)創(chuàng)建這個(gè)子類的實(shí)例并調(diào)用其start方法來(lái)啟動(dòng)線程。
class MyThread extends Thread { @Override public void run() { // 線程要執(zhí)行的代碼 System.out.println("線程運(yùn)行中..."); } } public class ThreadExample { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); // 啟動(dòng)線程 } }
方式二.實(shí)現(xiàn)Runnable接口
另一種創(chuàng)建線程的方式是實(shí)現(xiàn)Runnable接口。你需要?jiǎng)?chuàng)建一個(gè)實(shí)現(xiàn)了Runnable接口的類,然后創(chuàng)建該類的實(shí)例,并把這個(gè)實(shí)例傳遞給Thread類的構(gòu)造函數(shù)。最后,通過(guò)調(diào)用Thread對(duì)象的start方法來(lái)啟動(dòng)線程。
class MyRunnable implements Runnable { @Override public void run() { // 線程要執(zhí)行的代碼 System.out.println("線程運(yùn)行中..."); } } public class RunnableExample { public static void main(String[] args) { MyRunnable r = new MyRunnable(); Thread t = new Thread(r); t.start(); // 啟動(dòng)線程 } }
比較兩種方式
靈活性:實(shí)現(xiàn)Runnable接口比繼承Thread類更靈活,因?yàn)镴ava不支持多重繼承,但可以實(shí)現(xiàn)多個(gè)接口。
資源管理:如果你需要多個(gè)線程共享同一個(gè)資源,實(shí)現(xiàn)Runnable接口是更好的選擇,因?yàn)槟憧梢远x一個(gè)資源類,然后創(chuàng)建多個(gè)Runnable實(shí)例來(lái)共享這個(gè)資源。
代碼重用:實(shí)現(xiàn)Runnable接口允許你將線程的運(yùn)行代碼與線程的控制代碼分離,這有助于代碼重用。
在實(shí)際開(kāi)發(fā)中,推薦使用實(shí)現(xiàn)Runnable接口的方式來(lái)創(chuàng)建線程,因?yàn)樗峁┝烁玫撵`活性和代碼重用性,但也需考慮實(shí)際情況選擇使用。
線程生命周期
新建(New)、可運(yùn)行(Runnable)、阻塞(Blocked)、正在運(yùn)行(Running)、終止(Terminated)等狀態(tài)的解釋。
新建(New):
線程對(duì)象已經(jīng)被創(chuàng)建,但還沒(méi)有調(diào)用start()方法。在這個(gè)狀態(tài)下,線程還沒(méi)有開(kāi)始執(zhí)行。
可運(yùn)行(Runnable):
線程已經(jīng)調(diào)用了start()方法,此時(shí)線程處于可運(yùn)行狀態(tài)??蛇\(yùn)行狀態(tài)包括了操作系統(tǒng)線程的就緒(Ready)和運(yùn)行(Running)狀態(tài)。線程可能正在運(yùn)行,也可能正在等待CPU時(shí)間片,因?yàn)榭蛇\(yùn)行狀態(tài)的線程會(huì)與其他線程共享CPU資源。
阻塞(Blocked):
線程因?yàn)榈却粋€(gè)監(jiān)視器鎖(比如進(jìn)入一個(gè)同步塊)而無(wú)法繼續(xù)執(zhí)行的狀態(tài)。在這種情況下,線程會(huì)一直等待直到獲取到鎖。阻塞狀態(tài)通常發(fā)生在多個(gè)線程嘗試進(jìn)入一個(gè)同步方法或同步塊時(shí),但只有一個(gè)線程能夠獲得鎖。
正在運(yùn)行(Running):
線程正在執(zhí)行其run()方法的代碼。這個(gè)狀態(tài)是可運(yùn)行狀態(tài)的一個(gè)子集,表示線程當(dāng)前正在CPU上執(zhí)行。
注意:在Java官方文檔中,并沒(méi)有明確區(qū)分“可運(yùn)行”和“正在運(yùn)行”這兩個(gè)狀態(tài),通常將它們統(tǒng)稱為“可運(yùn)行(Runnable)”狀態(tài)。
終止(Terminated):
線程的運(yùn)行結(jié)束。這可能是因?yàn)榫€程正常執(zhí)行完任務(wù),或者因?yàn)槟硞€(gè)未捕獲的異常導(dǎo)致線程結(jié)束。一旦線程進(jìn)入終止?fàn)顟B(tài),它就不能再被啟動(dòng)或恢復(fù)。
線程同步
同步指的是在多線程環(huán)境中,控制多個(gè)線程對(duì)共享資源的訪問(wèn)順序,以防止數(shù)據(jù)不一致和競(jìng)態(tài)條件。同步機(jī)制確保了當(dāng)一個(gè)線程訪問(wèn)某個(gè)資源時(shí),其他線程不能同時(shí)訪問(wèn)該資源。
數(shù)據(jù)一致性:防止多個(gè)線程同時(shí)修改同一數(shù)據(jù),導(dǎo)致數(shù)據(jù)不一致。
線程安全:確保程序在多線程環(huán)境下能夠正確運(yùn)行,不會(huì)因?yàn)榫€程的并行執(zhí)行而出現(xiàn)錯(cuò)誤。
性能優(yōu)化:合理的同步可以提高程序的并發(fā)性能,避免不必要的線程阻塞和上下文切換。
synchronized關(guān)鍵字的使用
synchronized 是 Java 中用于同步的一個(gè)關(guān)鍵字,它可以用于方法或代碼塊,確保同一時(shí)間只有一個(gè)線程可以執(zhí)行該段代碼。
同步方法 public synchronized void myMethod() { // 需要同步的代碼 }
同步代碼塊 public void myMethod() { synchronized(this) { // 需要同步的代碼 } }
Locks&ReentrantLock
Java 提供了更靈活的鎖機(jī)制,稱為 Locks,其中最常用的是 ReentrantLock。
Locks:提供了比 synchronized 更靈活的鎖定機(jī)制,如嘗試鎖定、定時(shí)鎖定、可中斷的鎖定等。
ReentrantLock:是一種可重入的互斥鎖,支持完全的鎖定操作,可以被同一個(gè)線程多次獲得,但必須釋放相同次數(shù)。
使用 ReentrantLock 的基本步驟:
創(chuàng)建 ReentrantLock 對(duì)象。
在需要同步的代碼塊前后調(diào)用 lock() 和 unlock() 方法。
確保在 finally 塊中釋放鎖,以避免死鎖
import java.util.concurrent.locks.ReentrantLock; public class Example { private final ReentrantLock lock = new ReentrantLock(); public void myMethod() { lock.lock(); try { // 需要同步的代碼 } finally { lock.unlock(); } } }
線程同步是確保多線程程序正確性和性能的關(guān)鍵技術(shù)。synchronized 和 ReentrantLock 提供了不同的同步機(jī)制,開(kāi)發(fā)者可以根據(jù)具體需求選擇合適的同步方式。正確使用同步機(jī)制可以避免數(shù)據(jù)不一致和競(jìng)態(tài)條件,提高程序的穩(wěn)定性和性能。
線程間通信
線程間通信是多線程編程中的一個(gè)重要概念,它允許線程之間進(jìn)行數(shù)據(jù)交換和狀態(tài)同步。在 Java 中,線程間通信主要通過(guò)等待/通知機(jī)制和條件變量來(lái)實(shí)現(xiàn)。
等待/通知機(jī)制(wait()、notify()、notifyAll())
wait():當(dāng)一個(gè)線程調(diào)用 wait() 方法時(shí),它會(huì)釋放對(duì)象的鎖,并進(jìn)入該對(duì)象的等待池(wait set)中等待。其他線程可以調(diào)用 notify() 或 notifyAll() 方法來(lái)喚醒等待池中的線程。
notify():?jiǎn)拘言谠搶?duì)象上等待的單個(gè)線程。選擇哪個(gè)線程是不確定的。
notifyAll():?jiǎn)拘言谠搶?duì)象上等待的所有線程。
public class Message { private String content; private boolean empty = true; public synchronized String take() throws InterruptedException { while (empty) { wait(); } empty = true; notifyAll(); return content; } public synchronized void put(String content) throws InterruptedException { while (!empty) { wait(); } empty = false; this.content = content; notifyAll(); } }
條件變量(Condition)
條件變量提供了一種更靈活的線程間通信方式。Condition 接口是 java.util.concurrent.locks 包的一部分,它與 Lock 接口一起使用。
await():類似于 wait(),但需要在 Condition 對(duì)象上調(diào)用。
signal():類似于 notify(),但需要在 Condition 對(duì)象上調(diào)用。
signalAll():類似于 notifyAll(),但需要在 Condition 對(duì)象上調(diào)用。
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class Message { private String content; private boolean empty = true; private final ReentrantLock lock = new ReentrantLock(); private final Condition notEmpty = lock.newCondition(); public void put(String content) throws InterruptedException { lock.lock(); try { while (!empty) { notEmpty.await(); } empty = false; this.content = content; notEmpty.signal(); } finally { lock.unlock(); } } public String take() throws InterruptedException { lock.lock(); try { while (empty) { notEmpty.await(); } empty = true; String result = content; notEmpty.signal(); return result; } finally { lock.unlock(); } } }
線程池
線程池是一種執(zhí)行器(Executor),用于在一個(gè)后臺(tái)線程中執(zhí)行任務(wù)。線程池的主要目的是減少在創(chuàng)建和銷毀線程時(shí)所產(chǎn)生的性能開(kāi)銷。通過(guò)重用已經(jīng)創(chuàng)建的線程來(lái)執(zhí)行新的任務(wù),線程池提高了程序的響應(yīng)速度,并且提供了更好的系統(tǒng)資源管理。
Executor框架的使用
Java的java.util.concurrent包提供了Executor框架,它是一個(gè)用于管理線程的框架,包括線程池的管理。Executor框架的核心接口是Executor和ExecutorService。
Executor:一個(gè)執(zhí)行提交的Runnable任務(wù)的接口。
ExecutorService:Executor的子接口,提供了管理任務(wù)生命周期的方法,如關(guān)閉線程池、提交異步任務(wù)等。
如何創(chuàng)建和使用不同類型的線程池
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { // 創(chuàng)建一個(gè)固定大小的線程池 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4); // 創(chuàng)建一個(gè)緩存線程池 ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); // 創(chuàng)建一個(gè)單線程池 ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); // 提交任務(wù)給線程池 for (int i = 0; i < 10; i++) { final int index = i; fixedThreadPool.submit(() -?> { System.out.println("執(zhí)行任務(wù):" + index + " 線程:" + Thread.currentThread().getName()); }); } // 關(guān)閉線程池 fixedThreadPool.shutdown(); cachedThreadPool.shutdown(); singleThreadExecutor.shutdown(); } }
并發(fā)集合
傳統(tǒng)的集合類在多線程環(huán)境下的問(wèn)題
傳統(tǒng)的集合類(如 ArrayList、LinkedList、HashMap 等)并不是線程安全的。這意味著,如果在多線程環(huán)境下,多個(gè)線程同時(shí)對(duì)這些集合進(jìn)行讀寫(xiě)操作,可能會(huì)導(dǎo)致以下幾種問(wèn)題
數(shù)據(jù)不一致:當(dāng)多個(gè)線程同時(shí)修改集合時(shí),可能會(huì)導(dǎo)致集合的狀態(tài)不一致。例如,一個(gè)線程正在遍歷列表,而另一個(gè)線程正在添加或刪除元素,這可能導(dǎo)致遍歷過(guò)程中出現(xiàn) ConcurrentModificationException。
競(jìng)態(tài)條件:當(dāng)多個(gè)線程并發(fā)訪問(wèn)集合并且至少有一個(gè)線程在修改集合時(shí),就會(huì)發(fā)生競(jìng)態(tài)條件。這意味著最終結(jié)果依賴于線程執(zhí)行的順序,這可能導(dǎo)致不可預(yù)測(cè)的結(jié)果。
臟讀:一個(gè)線程可能讀取到另一個(gè)線程修改了一半的數(shù)據(jù),這種讀取被稱為“臟讀”。
幻讀:在一個(gè)事務(wù)中,多次查詢數(shù)據(jù)庫(kù),由于其他事務(wù)插入了行,導(dǎo)致原本滿足條件的查詢結(jié)果集中出現(xiàn)了“幻影”行。
不可重復(fù)讀:在一個(gè)事務(wù)內(nèi),多次讀取同一數(shù)據(jù)集合,由于其他線程的修改,導(dǎo)致每次都得到不同的數(shù)據(jù),這被稱為不可重復(fù)讀。
通過(guò)以下幾種策略解決多線程環(huán)境問(wèn)題
使用同步包裝器:Java提供了一些同步包裝器,如 Collections.synchronizedList、Collections.synchronizedMap 等,可以將非線程安全的集合包裝成線程安全的。
使用并發(fā)集合:Java的 java.util.concurrent 包提供了一些線程安全的集合類,如 ConcurrentHashMap、CopyOnWriteArrayList 等,它們內(nèi)部實(shí)現(xiàn)了必要的同步機(jī)制。
使用鎖:可以使用 synchronized 關(guān)鍵字或 ReentrantLock 對(duì)集合的操作進(jìn)行顯式同步。
使用原子類:對(duì)于基本數(shù)據(jù)類型的集合,可以使用 java.util.concurrent.atomic 包中的原子類,如 AtomicInteger、AtomicReference 等。
使用不可變集合:不可變集合一旦創(chuàng)建就不能被修改,因此是線程安全的??梢允褂?Collections.unmodifiableList、Collections.unmodifiableMap 等方法創(chuàng)建不可變集合。
使用線程局部變量:如果每個(gè)線程都需要有自己的集合副本,可以使用 ThreadLocal 類。
避免共享:如果可能,避免在多個(gè)線程間共享集合,每個(gè)線程使用獨(dú)立的集合可以避免同步問(wèn)題。
并發(fā)設(shè)計(jì)模式
生產(chǎn)者-消費(fèi)者模式(Producer-Consumer Pattern)
生產(chǎn)者-消費(fèi)者模式是一種常見(jiàn)的并發(fā)設(shè)計(jì)模式,用于協(xié)調(diào)生產(chǎn)者線程和消費(fèi)者線程之間的工作。生產(chǎn)者線程負(fù)責(zé)生成數(shù)據(jù),消費(fèi)者線程負(fù)責(zé)處理數(shù)據(jù)。它們之間通常通過(guò)一個(gè)共享的緩沖區(qū)(如隊(duì)列)進(jìn)行通信。這個(gè)模式可以有效地解耦生產(chǎn)者和消費(fèi)者的工作,提高程序的并發(fā)性能。
BlockingQueue queue = new LinkedBlockingQueue?>(); class Producer extends Thread { public void run() { while (true) { Work item = produce(); queue.put(item); } } Work produce() { // 生產(chǎn)數(shù)據(jù) return new Work(); } } class Consumer extends Thread { public void run() { while (true) { Work item = queue.take(); consume(item); } } void consume(Work item) { // 消費(fèi)數(shù)據(jù) } }
讀寫(xiě)鎖模式(Reader-Writer Lock Pattern)
讀寫(xiě)鎖模式允許多個(gè)線程同時(shí)讀取共享資源,但寫(xiě)入操作是互斥的。這種模式適用于讀多寫(xiě)少的場(chǎng)景,可以提高程序的并發(fā)性能。
class ReadWriteResource { private final ReadWriteLock lock = new ReentrantReadWriteLock(); public void read() { lock.readLock().lock(); try { // 執(zhí)行讀取操作 } finally { lock.readLock().unlock(); } } public void write() { lock.writeLock().lock(); try { // 執(zhí)行寫(xiě)入操作 } finally { lock.writeLock().unlock(); } } }
線程池模式(ThreadPool Pattern)
線程池模式通過(guò)復(fù)用一組線程來(lái)執(zhí)行多個(gè)任務(wù),減少了線程創(chuàng)建和銷毀的開(kāi)銷。線程池可以控制并發(fā)線程的數(shù)量,提高資源利用率。
ExecutorService executor = Executors.newFixedThreadPool(10); executor.submit(() -> { // 執(zhí)行任務(wù) }); executor.shutdown();
案例分析
寫(xiě)了幾個(gè)多線程并發(fā)的小demo,有需要可以聯(lián)系獲取倉(cāng)庫(kù)權(quán)限
注:文章有很多瑕疵,歡迎各位大佬批評(píng)指正
審核編輯 黃宇
-
測(cè)試
+關(guān)注
關(guān)注
8文章
5394瀏覽量
127123 -
代碼
+關(guān)注
關(guān)注
30文章
4835瀏覽量
69117
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
松下MPS媒體制作平臺(tái)第七篇:視頻混合器插件(第二部分)
![松下MPS媒體制作平臺(tái)第七<b class='flag-5'>篇</b>:視頻混合器插件(第二部分)](https://file1.elecfans.com/web3/M00/03/0E/wKgZO2djsdCAOF5sAAA-bD2DPQU515.png)
超詳細(xì)“零”基礎(chǔ)kafka入門(mén)篇
![超詳細(xì)“零”基礎(chǔ)kafka<b class='flag-5'>入門(mén)篇</b>](https://file1.elecfans.com/web3/M00/02/B6/wKgZPGdiKpWADaRzAABB8IHW8Yo599.png)
零基礎(chǔ)開(kāi)發(fā)小安派-Eyes-S1【入門(mén)篇】——工程文件架構(gòu)
![零基礎(chǔ)開(kāi)發(fā)小安派-Eyes-S1【<b class='flag-5'>入門(mén)篇</b>】——工程文件架構(gòu)](https://file1.elecfans.com//web2/M00/09/F1/wKgZomcXRTOAO29HAAAh-mmD6A001.jpeg)
迅為iTOP-RK3568開(kāi)發(fā)板驅(qū)動(dòng)開(kāi)發(fā)指南-第十八篇 PWM
【AG32開(kāi)發(fā)板體驗(yàn)連載】網(wǎng)絡(luò)攝像頭
重塑定位邊界:革新 UWB 信標(biāo)定位系統(tǒng)測(cè)試套件,精準(zhǔn)并發(fā)融合引領(lǐng)未來(lái)
![重塑定位邊界:革新 UWB 信標(biāo)定位系統(tǒng)<b class='flag-5'>測(cè)試</b>套件,精準(zhǔn)<b class='flag-5'>并發(fā)</b>融合引領(lǐng)未來(lái)](https://file1.elecfans.com/web2/M00/08/5D/wKgaombxFSSAfK1TAADDU_y1kcA725.png)
高并發(fā)物聯(lián)網(wǎng)云平臺(tái)是什么
高并發(fā)系統(tǒng)的藝術(shù):如何在流量洪峰中游刃有余
![高<b class='flag-5'>并發(fā)</b>系統(tǒng)的藝術(shù):如何在流量洪峰中游刃有余](https://file1.elecfans.com//web2/M00/00/97/wKgZomawZnKANlnmAAj89Sh41Aw003.png)
【《大語(yǔ)言模型應(yīng)用指南》閱讀體驗(yàn)】+ 俯瞰全書(shū)
文檔更新 |迅為 RK3568開(kāi)發(fā)板驅(qū)動(dòng)指南-第十五/十六篇
性能測(cè)試主要測(cè)什么 性能測(cè)試的指標(biāo)有哪些
聊一聊“阻抗修正”去嵌入
![<b class='flag-5'>聊</b>一<b class='flag-5'>聊</b>“阻抗修正”去嵌入](https://file1.elecfans.com/web2/M00/CB/DA/wKgaomYfamuAKO_DAAATOwYVUvE329.png)
評(píng)論