如何理解Java中的并發(fā)?
Java 中的并發(fā)(Concurrency) 指多個任務(wù)在同一時間段內(nèi)交替執(zhí)行(宏觀上同時進行,微觀上可能是 CPU 快速切換調(diào)度),目的是提高程序效率,充分利用系統(tǒng)資源(如 CPU、內(nèi)存、I/O 等)。
一、為什么需要并發(fā)?
-
資源利用率最大化
當(dāng)程序執(zhí)行 I/O 操作(如讀寫文件、網(wǎng)絡(luò)請求)時,CPU 通常處于空閑狀態(tài)。通過并發(fā),可在等待 I/O 時讓 CPU 處理其他任務(wù),避免資源浪費。
例如:一個下載文件的程序,在等待網(wǎng)絡(luò)數(shù)據(jù)時,可同時解析已下載的部分?jǐn)?shù)據(jù)。 -
響應(yīng)速度提升
對于交互式程序(如 GUI 應(yīng)用、服務(wù)器),并發(fā)能避免單任務(wù)阻塞導(dǎo)致的界面卡頓或請求超時。
例如:Web 服務(wù)器同時處理多個用戶的請求,而非逐個排隊處理。
二、并發(fā)的核心概念
1. 線程(Thread)與進程(Process)
- 進程:程序的一次執(zhí)行過程,是系統(tǒng)資源分配的基本單位(有獨立的內(nèi)存空間)。
- 線程:進程內(nèi)的執(zhí)行單元,是 CPU 調(diào)度的基本單位(共享進程的內(nèi)存空間)。
- 關(guān)系:一個進程可包含多個線程(多線程),線程間切換成本遠低于進程切換。
2. 并行(Parallelism)與并發(fā)(Concurrency)的區(qū)別
- 并發(fā):多個任務(wù)“交替執(zhí)行”(CPU 切換速度快,看起來同時進行),適用于單 CPU 或多 CPU。
- 并行:多個任務(wù)“同時執(zhí)行”(需多 CPU 核心,每個核心處理一個任務(wù))。
例如:4 核 CPU 同時運行 4 個線程是并行,1 核 CPU 快速切換 4 個線程是并發(fā)。
三、Java 實現(xiàn)并發(fā)的方式
Java 提供了多種并發(fā)編程工具,核心是通過線程實現(xiàn):
1. 基礎(chǔ)方式
- 繼承
Thread類:重寫run()方法定義任務(wù),調(diào)用start()啟動線程。 - 實現(xiàn)
Runnable接口:定義任務(wù)邏輯,通過Thread類包裝并啟動(推薦,避免單繼承限制)。 -
實現(xiàn)
Callable接口:與Runnable類似,但可返回結(jié)果并拋出異常,配合Future獲取結(jié)果。// Callable 示例 import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { // 1. 定義任務(wù)(有返回值) Callable<Integer> task = () -> { int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } return sum; }; // 2. 包裝任務(wù) FutureTask<Integer> futureTask = new FutureTask<>(task); // 3. 啟動線程 new Thread(futureTask).start(); // 4. 獲取結(jié)果(會阻塞直到任務(wù)完成) System.out.println("1-100的和:" + futureTask.get()); // 輸出5050 } }
2. 線程池(ThreadPoolExecutor)
頻繁創(chuàng)建/銷毀線程會消耗資源,線程池通過復(fù)用線程提高效率,是生產(chǎn)環(huán)境的首選。
Java 提供 Executors 工具類快速創(chuàng)建線程池:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 創(chuàng)建固定大小的線程池(3個線程)
ExecutorService pool = Executors.newFixedThreadPool(3);
// 提交5個任務(wù)(線程池會復(fù)用3個線程處理)
for (int i = 0; i < 5; i++) {
int taskId = i;
pool.submit(() -> {
System.out.println("處理任務(wù)" + taskId + ",線程:" + Thread.currentThread().getName());
});
}
// 關(guān)閉線程池
pool.shutdown();
}
}
四、并發(fā)帶來的問題及解決方案
并發(fā)雖提高效率,但多線程共享資源時會引發(fā)問題:
1. 線程安全問題
當(dāng)多個線程同時操作共享數(shù)據(jù)(如全局變量、集合),可能導(dǎo)致數(shù)據(jù)不一致。
示例:兩個線程同時對變量 count 做 ++ 操作,預(yù)期結(jié)果為 2,實際可能為 1(因 ++ 是多步操作,可能被打斷)。
2. 解決方案
-
synchronized關(guān)鍵字:通過“鎖”保證同一時間只有一個線程執(zhí)行臨界區(qū)代碼(修飾方法或代碼塊)。public class SynchronizedDemo { private static int count = 0; private static final Object lock = new Object(); // 鎖對象 public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i < 10000; i++) { synchronized (lock) { // 同步代碼塊:同一時間只有一個線程進入 count++; } } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 10000; i++) { synchronized (lock) { count++; } } }); t1.start(); t2.start(); t1.join(); // 等待線程執(zhí)行完畢 t2.join(); System.out.println("count最終值:" + count); // 正確輸出20000 } } -
java.util.concurrent工具類:提供線程安全的集合(如ConcurrentHashMap)、原子類(如AtomicInteger)、鎖機制(如ReentrantLock)等,比synchronized更靈活。
五、并發(fā)編程的核心挑戰(zhàn)
-
可見性:一個線程修改的共享變量,其他線程可能無法立即看到(因 CPU 緩存導(dǎo)致)。
解決方案:使用volatile關(guān)鍵字(保證變量修改后立即刷新到主內(nèi)存)。 -
原子性:一個操作不可被中斷(如
count++實際是“讀-改-寫”三步,非原子操作)。
解決方案:synchronized、原子類(AtomicInteger)。 -
有序性:CPU 可能對指令重排序優(yōu)化,導(dǎo)致代碼執(zhí)行順序與預(yù)期不一致。
解決方案:volatile、synchronized或顯式內(nèi)存屏障。
六、總結(jié)
- 并發(fā)的本質(zhì):通過多線程交替執(zhí)行,提高資源利用率和程序響應(yīng)速度。
- 核心問題:線程安全(數(shù)據(jù)不一致),需通過鎖機制或并發(fā)工具解決。
- 實踐建議:優(yōu)先使用線程池管理線程,避免手動創(chuàng)建;復(fù)雜場景下借助
java.util.concurrent包的工具類(如CountDownLatch、Semaphore)簡化開發(fā)。
浙公網(wǎng)安備 33010602011771號