基礎概念
Java 線程是并發編程的基礎,涉及到線程的創建、管理、同步以及通信。理解和掌握線程的使用對于編寫高效和響應快速的應用程序至關重要。
1. 線程基礎
線程是程序中的執行流。每個Java程序至少有一個線程 — 主線程(main)。通過使用Thread類和Runnable接口,可以在Java中創建和管理線程。
線程創建
有兩種主要方式來創建線程:
繼承Thread類:創建一個新的類繼承Thread類,并重寫其run()方法。
實現Runnable接口:創建一個實現了Runnable接口的類,然后將其實例傳遞給Thread類的構造函數。
// 通過繼承Thread類創建線程
class MyThread extends Thread {public void run() {System.out.println("Thread running: " + Thread.currentThread().getName());}
}// 通過實現Runnable接口創建線程
class MyRunnable implements Runnable {public void run() {System.out.println("Runnable running: " + Thread.currentThread().getName());}
}public class ThreadExample {public static void main(String[] args) {MyThread t1 = new MyThread();t1.start();Thread t2 = new Thread(new MyRunnable());t2.start();}
}
2. 線程狀態
Java線程可以處于以下狀態之一:
新建(New):線程實例已被創建,但尚未開始執行。
可運行(Runnable):線程正在Java虛擬機中執行,但它可能正在等待操作系統的其他資源(如CPU分配)。
阻塞(Blocked):線程被阻止,無法進行,因為它正在等待一個監視器鎖。
等待(Waiting):線程通過調用wait()、join()或LockSupport.park()進入等待狀態。
計時等待(Timed Waiting):類似于等待狀態,但它會在指定的時間自動返回。
終止(Terminated):線程已完成執行或因異常退出。
3. 線程同步
為了防止多個線程訪問共享資源而引發數據不一致的問題,需要進行線程同步。
synchronized
synchronized關鍵字可以用來同步方法或代碼塊。同步方法或代碼塊確保一次只有一個線程可以執行它們。
public class Counter {private int count = 0;// 同步方法public synchronized void increment() {count++;}public int getCount() {return count;}
}
4. 線程通信
線程之間的通信常通過等待/通知機制實現 —— 即wait()、notify()和notifyAll()方法。
public class WaitNotifyExample {public static void main(String[] args) {final Object lock = new Object();Thread thread1 = new Thread(() -> {synchronized (lock) {try {System.out.println("Thread 1: Waiting for lock");lock.wait();System.out.println("Thread 1: Woken up");} catch (InterruptedException e) {e.printStackTrace();}}});Thread thread2 = new Thread(() -> {synchronized (lock) {System.out.println("Thread 2: Sleeping for 1 second");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 2: Notifying");lock.notify();}});thread1.start();thread2.start();}
}
5. 并發工具
Java還提供了如ExecutorService, CountDownLatch, CyclicBarrier, Semaphore, Future, Callable等高級并發工具,這些都是構建復雜的并發應用程序時不可或缺的工具。
進階概念
1. 線程池(Executor Framework)
線程池是一種基于池化技術的線程管理解決方案。使用線程池可以減少在創建和銷毀線程上所花的開銷,同時可以提供對并發任務執行的細粒度控制。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class ThreadPoolExample {public static void main(String[] args) {// 創建固定大小的線程池ExecutorService executor = Executors.newFixedThreadPool(4);// 提交任務給線程池for (int i = 0; i < 10; i++) {int taskNumber = i;executor.submit(() -> {System.out.println("Executing Task " + taskNumber + " by " + Thread.currentThread().getName());});}// 關閉線程池executor.shutdown();try {executor.awaitTermination(1, TimeUnit.DAYS);} catch (InterruptedException e) {e.printStackTrace();}}
}
2. 并發集合
Java 提供了多種線程安全的集合類,如 ConcurrentHashMap, CopyOnWriteArrayList 等。這些集合類優化了多線程環境下的性能,減少了鎖的競爭。
import java.util.concurrent.ConcurrentHashMap;public class ConcurrentMapExample {public static void main(String[] args) {ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();// 線程安全地更新 ConcurrentHashMapmap.put("key", "initial value");map.compute("key", (k, v) -> v + ", updated value");System.out.println(map.get("key"));}
}
3. 原子變量
Java 的 java.util.concurrent.atomic 包提供了一系列的原子類,用于在無鎖的情況下進行線程安全的操作,這些操作包括單一變量的讀取、寫入、更新等。
import java.util.concurrent.atomic.AtomicInteger;public class AtomicExample {public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(0);// 原子方式更新整數atomicInteger.incrementAndGet(); // 增加1System.out.println("Current Value: " + atomicInteger.get());}
}
4. CompletableFuture
CompletableFuture 提供了一個非阻塞的方式來處理并發編程。通過CompletableFuture,你可以編寫異步的、非阻塞的代碼,處理異步的計算結果。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;public class CompletableFutureExample {public static void main(String[] args) throws ExecutionException, InterruptedException {CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new IllegalStateException(e);}return "Result of the asynchronous computation";});// 獲取結果,等待異步操作結束String result = completableFuture.get();System.out.println(result);}
}
5. 高級同步技術
學習如 CyclicBarrier, Semaphore, 和 Phaser 等同步輔助類,這些類可以協調多個線程間復雜的交互和同步。
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.BrokenBarrierException;public class CyclicBarrierExample {private static class Task implements Runnable {private CyclicBarrier barrier;public Task(CyclicBarrier barrier) {this.barrier = barrier;}public void run() {try {System.out.println(Thread.currentThread().getName() + " is waiting on barrier");barrier.await();System.out.println(Thread.currentThread().getName() + " has crossed the barrier");} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}}public static void main(String[] args) {final CyclicBarrier cb = new CyclicBarrier(3, () -> System.out.println("All parties are arrived at barrier, lets play"));Thread t1 = new Thread(new Task(cb), "Thread 1");Thread t2 = new Thread(new Task(cb), "Thread 2");Thread t3 = new Thread(new Task(cb), "Thread 3");t1.start();t2.start();t3.start();}
}
面試相關問題
1. Java中創建線程的方法有哪些?
在Java中,有兩種主要的方式來創建線程:
繼承Thread類:創建一個類繼承Thread類,并重寫run()方法。然后創建這個類的實例并調用其start()方法來啟動新線程。
實現Runnable接口:創建一個實現了Runnable接口的類,實現run()方法。然后創建這個類的實例,把它作為參數傳遞給Thread類的構造器,然后調用Thread的start()方法來啟動線程。
2. 什么是線程安全?請舉例說明。
線程安全意味著在多線程環境中,無論運行時操作系統如何調度這些線程,以及線程如何交替執行,代碼都能表現出正確的行為。換句話說,線程安全的代碼能夠保護數據免受多個線程同時執行時的沖突和錯誤狀態。
例如,java.util.concurrent.ConcurrentHashMap是線程安全的,它可以在不進行額外同步的情況下被多個線程同時訪問和修改,而java.util.HashMap就不是線程安全的。
3. 解釋synchronized關鍵字及其工作原理。
synchronized是Java中的一個關鍵字,用于加鎖,以便在同一時刻只允許一個線程訪問關鍵部分代碼。它可以用于同步方法和同步塊。
當synchronized用于方法時,鎖是對當前對象實例加鎖。
當synchronized用于靜態方法時,鎖是對當前類的Class對象加鎖。
當synchronized用于代碼塊時,它必須給定一個對象引用,鎖是對這個對象引用加鎖。
synchronized確保只有持有對象鎖的線程才能執行同步代碼,從而防止多個線程同時執行相同代碼塊造成的數據不一致問題。
4. 什么是死鎖?如何避免死鎖?
死鎖是指兩個或多個線程在執行過程中,因爭奪資源而造成的一種僵局,每個線程都在等待其他線程釋放它所需的資源。為了避免死鎖,可以采用以下措施:
避免多個線程同時持有多個鎖:盡量確保每個線程一次只持有一個鎖。
鎖順序:規定一個全局的鎖的順序,并且按照這個順序來獲取鎖。
使用tryLock():使用帶有超時的tryLock()方法,這樣線程在等待鎖的時候,如果不能立即得到鎖,就會放棄、回退、然后重新嘗試,從而減少死鎖的可能性。
使用死鎖檢測工具:利用一些工具來檢測和恢復死鎖。
5. 解釋Java中的volatile關鍵字。
volatile是Java語言提供的最輕量級的同步機制。用volatile聲明的變量可以確保每次讀取都從主存中進行,每次寫入都到主存中,從而保證了該變量的可見性。volatile并不執行互斥訪問,但它禁止指令重排序優化,使得賦值操作不會與之前的寫操作重排序,也不會與之后的讀操作重排序。
總體知識
1. Java內存模型(Java Memory Model, JMM)
理解Java內存模型是至關重要的,因為它定義了線程如何通過內存進行交互。Java內存模型確保不同線程間的可見性和有序性,通過使用volatile、synchronized等關鍵字實現。
可見性:保證一個線程對共享變量的修改,對于其他線程是可見的。
原子性:操作在其他線程看來是不可分割的。
有序性:保證程序執行的順序按照代碼的先后順序執行。
Java 內存模型(JMM,Java Memory Model)是理解 Java 多線程編程中線程間通信的核心概念。JMM 定義了線程和主內存之間的抽象關系,并規定了線程如何以及何時可以看到由其他線程修改過的共享變量的值,以及如何同步訪問共享變量。
1. JMM的主要目標
JMM的設計目的主要是為了解決可見性、原子性和有序性這三個問題。
可見性:一個線程對共享變量的修改,其他線程能夠立即得知這個修改。
原子性:操作在多個線程中不可被中斷的執行過程。
有序性:程序執行的順序按照代碼的先后順序執行。
2. 內存模型的組成
JMM 把變量存儲分為主內存和工作內存兩種:
主內存(Main Memory):主內存直接與Java堆相關,存儲實例字段、靜態字段和構成數組對象的元素等。
工作內存(Working Memory):每個線程都有自己的工作內存,它包含了該線程用到的變量的主內存副本拷貝。
3. 內存交互操作
JMM定義了8種操作來完成主內存與工作內存之間的交互:
lock(鎖定):作用于主內存的變量,它標記一個變量被一個線程獨占狀態。
unlock(解鎖):作用于主內存的變量,它標記一個變量結束獨占狀態。
read(讀取):作用于主內存的變量,把一個變量的值從主內存傳輸到線程的工作內存中,以便隨后的load動作使用。
load(載入):作用于工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。
use(使用):作用于工作內存的變量,它把工作內存中一個變量的值傳遞給執行引擎。
assign(賦值):作用于工作內存的變量,它把一個從執行引擎接收到的值賦給工作內存的變量。
store(存儲):作用于工作內存的變量,它把工作內存中一個變量的值傳送到主內存中,以便隨后的write的操作。
write(寫入):作用于主內存的變量,它把store操作從工作內存中得到的變量的值放入主內存的變量中。
4. 內存可見性保證
Java內存模型通過volatile、synchronized和final等關鍵字來提供內存可見性保證。
volatile:保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個volatile變量的值,這新值對其他線程來說是立即可見的,并禁止指令重排。
synchronized:保證了同一時刻只有一個線程可以執行同步代碼段,并且入口前必須獲得鎖,退出時必須釋放鎖,從而保證了鎖內語句對變量的修改對所有線程都可見。
final:被final修飾的字段,線程安全性得到了保證,對final字段的寫入早于對這個字段的首次讀取。
5. 指令重排
JMM 允許編譯器和處理器對操作指令進行重排序,只要重排序不改變單線程程序的執行結果。這樣做是為了提高性能。但在多線程環境下,這種重排序可能會導致嚴重問題。volatile和synchronized可以在一定程度上約束JVM和Java程序的重排序行為。
2. 鎖優化
理解并能實現各種鎖優化技術是必需的:
輕量級鎖:JVM在運行時對于無競爭的鎖進行優化,避免重量級的操作系統互斥元操作。
偏向鎖:一種優化技術,假設最后一個獲得鎖的線程將頻繁鎖定同一個對象。
鎖消除:JVM優化去除不可能共享數據之間的鎖操作。
鎖粗化:將多個連續的鎖擴展為一個更大范圍的鎖,以減少鎖獲取的次數。
在Java中,鎖是同步不同線程間對共享資源訪問的一種機制。為了提高性能,Java虛擬機(JVM)提供了多種鎖優化技術。理解這些鎖優化方法對于編寫高效的多線程代碼非常重要。
1. 偏向鎖(Biased Locking)
偏向鎖是一種鎖優化技術,它假設鎖會被同一個線程多次重復獲取。當鎖被一個線程獲取之后,JVM會在鎖對象的頭部標記這個線程的ID,之后這個線程再次請求鎖時,JVM只需簡單檢查這個ID,無需進行完整的鎖獲取過程。
優點:減少了鎖獲取的開銷,適用于只有單一線程訪問同步塊的情況。
缺點:如果多個線程交替競爭同一個鎖,偏向鎖會被撤銷,可能增加額外的撤銷成本。
2. 輕量級鎖(Lightweight Locking)
當偏向鎖被撤銷,但鎖競爭不激烈時,JVM會使用輕量級鎖。輕量級鎖通過在棧幀中創建鎖記錄(Lock Record)來存儲鎖標記。如果沒有競爭,線程可以通過CAS(Compare-And-Swap)操作將鎖對象的頭部指向鎖記錄來獲取鎖。
優點:避免了在無競爭情況下的重量級操作系統互斥量的使用。
缺點:在有競爭的情況下,線程需要自旋等待鎖釋放,可能導致CPU資源浪費。
3. 重量級鎖(Heavyweight Locking)
如果輕量級鎖的自旋失敗,即存在鎖競爭,JVM將升級鎖為重量級鎖。重量級鎖依賴于操作系統的互斥量(mutex)來實現線程的阻塞和喚醒,以控制對臨界區的訪問。
優點:適用于鎖競爭激烈的情況,有效避免了線程的無限自旋。
缺點:依賴操作系統功能,涉及到用戶態到內核態的切換,性能開銷較大。
4. 鎖粗化(Lock Coarsening)
如果JVM檢測到一系列的連續鎖操作(在循環中頻繁地加鎖和解鎖同一個對象),它可能會將鎖的范圍擴大(粗化),以包括整個操作序列,從而減少鎖獲取和釋放的次數。
優點:減少鎖操作的總次數,提高執行效率。
缺點:如果操作序列中包含不需要同步的操作,可能導致不必要的性能損失。
5. 鎖消除(Lock Elimination)
JVM通過逃逸分析來檢測某些鎖操作是否是多余的。如果JVM確定一段代碼中的鎖對象不可能被其他線程訪問(對象的作用域不會逃出當前線程),則JVM可以消除這些鎖操作。
優點:減少不必要的鎖操作,提高性能。
缺點:依賴JVM的逃逸分析的準確性。
public class LockOptimizationExample {private static final Object lock = new Object();public static void main(String[] args) {// 偏向鎖示例(需要在JVM啟動參數中開啟偏向鎖)synchronized (lock) {System.out.println("Hello from biased locking!");}// 輕量級鎖和重量級鎖的轉換new Thread(() -> {synchronized (lock) {try {Thread.sleep(1000); // 模擬長時間操作} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}).start();synchronized (lock) {System.out.println("Hello from main thread!");}}
}
3. 并發工具
掌握Java提供的并發工具類,如java.util.concurrent包中的類,這些工具可以幫助處理復雜的并發場景:
Executors框架:管理線程池和執行異步任務。
并發集合:如ConcurrentHashMap, CopyOnWriteArrayList等,這些集合提供更好的并發性能。
同步器:如CountDownLatch, CyclicBarrier, Semaphore, Phaser等,幫助同步多個線程的操作。
Future和Callable:支持有返回結果的并行計算。
CompletableFuture:提供非阻塞的異步編程方式。
Java并發工具主要來自java.util.concurrent包,這個包提供了一系列用于處理多線程編程的高級同步器和并發集合類。這些工具的設計旨在幫助開發者更高效地管理和控制線程間的并發操作。
1. 執行器框架(Executor Framework)
Java的執行器框架抽象了線程的創建、管理和執行過程,使得開發者可以更專注于任務的執行而非線程管理。核心接口和類包括:
Executor:一個簡單的接口,用于執行提交的Runnable任務。
ExecutorService:一個更完整的異步任務執行接口,它繼承了Executor,添加了生命周期管理和任務執行結果追蹤的功能。
ScheduledExecutorService:一個能處理延遲或定期執行任務的接口。
Executors:一個工具類,提供了構造執行器的工廠方法。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ExecutorExample {public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(3);// 提交任務給線程池for (int i = 0; i < 5; i++) {int taskId = i;executor.submit(() -> {System.out.println("Executing task " + taskId + " via " + Thread.currentThread().getName());});}executor.shutdown(); // 關閉線程池}
}
2. 并發集合
并發集合是特別設計用于多線程環境的集合,提供了線程安全的集合操作,無需外部同步。
ConcurrentHashMap:一個高效的線程安全的哈希表。
CopyOnWriteArrayList:一個線程安全的List,通過復制底層數組的方式實現修改操作,非常適合讀多寫少的場景。
BlockingQueue:一個支持兩個附加操作的隊列,即在隊列為空時取元素的線程會等待隊列變為非空,隊列滿時插入元素的線程會等待隊列可用。
import java.util.concurrent.ConcurrentHashMap;public class ConcurrentHashMapExample {public static void main(String[] args) {ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();map.put("key", "value");System.out.println(map.get("key"));}
}
3. 同步器
Java提供了多種同步器,這些同步器為多個線程之間的協作提供了控制機制。
CountDownLatch:允許一個或多個線程等待其他線程完成操作。
CyclicBarrier:使一定數量的線程在某個公共點同步。
Semaphore:控制對某組資源的訪問權限。
Phaser:一個更靈活的線程同步設備,可以用于執行已經完成的階段中的重復動作。
import java.util.concurrent.Semaphore;public class SemaphoreExample {public static void main(String[] args) {Semaphore semaphore = new Semaphore(2);Runnable longRunningTask = () -> {boolean permit = false;try {permit = semaphore.tryAcquire();if (permit) {System.out.println("Semaphore acquired");Thread.sleep(5000);} else {System.out.println("Could not acquire semaphore");}} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {if (permit) {semaphore.release();}}};Thread t1 = new Thread(longRunningTask);Thread t2 = new Thread(longRunningTask);t1.start();t2.start();}
}
4. Future和Callable
這些工具用于處理那些需要返回結果的任務。Callable是類似于Runnable的接口,不過它可以返回一個結果并能拋出異常。
import java.util.concurrent.*;public class CallableFutureExample {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService executor = Executors.newSingleThreadExecutor();Callable<Integer> task = () -> {TimeUnit.SECONDS.sleep(2);return 123;};Future<Integer> future = executor.submit(task);System.out.println("future done? " + future.isDone());Integer result = future.get(); // 阻塞直到任務完成System.out.println("future done? " + future.isDone());System.out.println("result: " + result);executor.shutdown();}
}
4. 線程安全策略
熟練使用不同的線程安全策略,例如:
不變性:使用不可變對象安全地管理數據共享。
線程局部存儲:使用ThreadLocal類,為每個線程提供單獨的變量副本。
使用原子類:如AtomicInteger等,利用CAS操作提供無鎖的線程安全性。
線程安全是多線程程序設計中的核心概念,特別是在Java這樣的并發編程語言中。線程安全意味著在多個線程訪問共享資源時,無論運行時環境采用何種調度方式或線程如何交替執行,程序都能表現出正確的行為。為了達到線程安全,開發者需要采用多種策略來保護數據不被并發的線程訪問所破壞。
1. 不可變性
將數據聲明為不可變是實現線程安全的一種簡單而強大的策略。不可變對象一旦被創建,其狀態就不可改變,因此無需同步就可以被多個線程安全地訪問。
實現方法:在Java中,不可變對象可以通過將所有成員變量聲明為final并僅通過構造函數設置它們的值來創建。
優點:簡單、安全且易于理解和實現。
public final class ImmutableValue {private final int value;public ImmutableValue(int value) {this.value = value;}public int getValue() {return value;}
}
2. 封鎖
對于必須可變的數據,保護其訪問的傳統方式是使用鎖。在Java中,可以使用synchronized關鍵字或顯示鎖(如ReentrantLock)來同步代碼塊,確保一次只有一個線程可以執行該代碼塊。
使用synchronized:可以鎖定一個對象或一個類,為調用鎖定對象或類的方法的線程提供獨占訪問。
使用ReentrantLock:提供比synchronized更靈活的鎖定機制,包括嘗試非阻塞獲取鎖、嘗試獲取鎖并可中斷地等待獲取鎖,以及支持公平鎖等。
public class Counter {private int count = 0;private final Object lock = new Object();public void increment() {synchronized (lock) {count++;}}public synchronized int getCount() {return count;}
}
3. 無鎖并發控制
現代多核CPU支持原子指令集,使得無鎖編程成為可能。Java的java.util.concurrent.atomic包提供了一系列原子類,如AtomicInteger和AtomicReference,它們使用高效的機器級指令(如CAS —— 比較并交換)來避免使用鎖。
優點:在高度競爭的環境下,無鎖機制通常比傳統的鎖機制提供更好的性能。
缺點:編程模型更復雜,邏輯處理更困難。
import java.util.concurrent.atomic.AtomicInteger;public class AtomicCounter {private final AtomicInteger count = new AtomicInteger(0);public void increment() {count.incrementAndGet();}public int getCount() {return count.get();}
}
4. 線程局部存儲
如果一個數據結構只被一個線程訪問,那么它自然是線程安全的。ThreadLocal類在Java中提供了線程局部變量。每個線程都可以訪問自己的、獨立初始化的變量副本。
使用場景:經常用于維護線程特定的狀態,如用戶會話或事務上下文。
public class ThreadLocalExample {private static final ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 1);public static void main(String[] args) {Thread thread1 = new Thread(() -> {threadLocalValue.set(100);System.out.println("Thread1: " +
5. Java并發模式
理解并能應用各種并發模式,如生產者-消費者、讀寫鎖、雙檢鎖單例等。
import java.util.concurrent.ConcurrentHashMap;public class ConcurrentHashMapExample {public static void main(String[] args) {ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();map.put("key", 1);// 使用原子方法 computeIfPresent 更新map.computeIfPresent("key", (k, v) -> v + 1);System.out.println(map.get("key")); // 輸出: 2}
}
Java 并發模式是在多線程環境中有效管理和組織線程行為的設計模式,這些模式解決了并發程序設計中的典型問題,如資源共享、線程協作等。了解和應用這些并發模式可以幫助開發者編寫更清晰、更高效、更穩定的并發代碼。
1. 生產者-消費者模式
生產者-消費者模式是一種經典的并發模式,用于處理生成數據的線程(生產者)和處理數據的線程(消費者)之間的協作。它通常通過一個共享的緩沖區來實現,生產者將數據放入緩沖區,消費者從緩沖區取出數據。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class ProducerConsumerExample {private static BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);static class Producer extends Thread {public void run() {for (int i = 0; i < 20; i++) {try {queue.put(i);System.out.println("Produced: " + i);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}}static class Consumer extends Thread {public void run() {while (true) {try {Integer value = queue.take();System.out.println("Consumed: " + value);Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}}public static void main(String[] args) {new Producer().start();new Consumer().start();}
}
2. 讀寫鎖模式
讀寫鎖模式允許多個線程同時讀取共享數據,但一個時間內只允許一個線程修改數據。這種模式通過java.util.concurrent.locks.ReadWriteLock接口實現,通常使用ReentrantReadWriteLock類。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockExample {private ReadWriteLock lock = new ReentrantReadWriteLock();private int value;public void setValue(int value) {lock.writeLock().lock();try {this.value = value;} finally {lock.writeLock().unlock();}}public int getValue() {lock.readLock().lock();try {return value;} finally {lock.readLock().unlock();}}
}
3. 工作竊取模式
工作竊取模式用于分散工作負載,特別適用于任務數量和執行時間不均勻的情況。在這個模式中,每個線程都維護自己的任務隊列。如果一個線程完成了所有任務,它可以從其他線程的隊列中竊取任務來執行。
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;public class ForkJoinExample {static class SumTask extends RecursiveTask<Integer> {private final int[] numbers;private final int start;private final int end;public SumTask(int[] numbers, int start, int end) {this.numbers = numbers;this.start = start;this.end = end;}@Overrideprotected Integer compute() {if (end - start < 20) {return java.util.Arrays.stream(numbers, start, end).sum();} else {int mid = start + (end - start) / 2;SumTask left = new SumTask(numbers, start, mid);SumTask right = new SumTask(numbers, mid, end);right.fork(); // 異步執行右側任務return left.compute() + right.join(); // 等待并獲取右側任務的結果}}}public static void main(String[] args) {ForkJoinPool pool = new ForkJoinPool();int[] numbers = new int[100];for (int i = 0; i < numbers.length; i++) {numbers[i] = i;}int result = pool.invoke(new SumTask(numbers, 0, numbers.length));System.out.println("Result: " + result);}
}
4. 雙檢鎖/單例模式
雙重檢查鎖模式(Double-Checked Locking)是一種用于延遲初始化資源的技術。它特別適用于實現單例模式,確保只創建單個實例,且在多線程環境中線程安全。
public class Singleton {private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
6. 避免常見的并發問題
理解并避免死鎖、活鎖、饑餓等常見的并發問題。使用工具和技術(如JVisualVM, Thread Dumps等)來檢測和診斷這些問題。
1. 數據競爭與競態條件
數據競爭發生在兩個或多個線程同時訪問相同的數據,并且至少有一個線程在寫入時。競態條件是指程序的行為依賴于多個線程的交錯執行時序。
解決策略:
鎖定:使用互斥鎖(mutexes),如synchronized關鍵字或ReentrantLock,確保同一時間只有一個線程可以訪問共享資源。
原子操作:使用原子類,如AtomicInteger,這些類利用CPU提供的原子指令集來保證操作的原子性。
避免共享狀態:盡可能設計無狀態或只讀的共享資源。利用局部變量和不可變對象可以減少同步需求。
2. 死鎖
死鎖是指兩個或多個進程或線程在執行過程中,因爭奪資源而造成的一種相互等待的現象,若無外力作用,它們都將無法推進下去。
解決策略:
鎖順序:定義一個全局的鎖順序,并且在代碼中嚴格按照這個順序來獲取鎖。
超時嘗試:使用帶超時的鎖嘗試機制,例如tryLock(long timeout, TimeUnit unit)方法,可以在超時后釋放鎖,防止永久等待。
死鎖檢測工具:利用Java中的JMX和工具(如jConsole、VisualVM)來監控和檢測死鎖。
3. 活鎖
活鎖類似于死鎖,涉及的線程或進程不斷重復相同的交互,沒有進展。這通常發生在兩個線程都在嘗試避免沖突并恢復對方原狀的情況下。
解決策略:
隨機等待:在重試之前引入隨機的延遲,以減少兩個線程或進程再次沖突的可能性。
優先級:為交互的線程引入優先級,允許優先級高的線程有更多的執行機會。
4. 饑餓
饑餓是指一個或多個線程無法獲得必要的資源,以至于無法進行有效的進展,這通常是由于資源被其他“貪婪”的線程長時間占用所致。
解決策略:
公平鎖:使用公平鎖機制,如ReentrantLock(true),確保線程按照請求鎖的順序來獲得鎖。
優先級調整:調整線程的優先級,以確保低優先級的線程不會永久餓死。
5. 內存泄漏
在并發環境中,內存泄漏通常發生在緩存、監聽器和其他共享資源中,當它們的生命周期被錯誤管理時。
解決策略:
弱引用:使用WeakReference和WeakHashMap等,允許垃圾收集器自動清理只被弱引用持有的對象。
定期清理:為使用的資源如緩存等定期清理機制,移除不再使用的條目。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class AvoidDeadlock {private final Lock lock1 = new ReentrantLock(true);private final Lock lock2 = new ReentrantLock(true);public void method1() {lock1.lock();try {// 模擬處理lock2.lock();try {System.out.println("Method 1 is running");} finally {lock2.unlock();}} finally {lock1.unlock();}}