信號量在Java中主要用于控制共享資源的并發訪問數量,其核心原理是通過維護許可計數器限制線程訪問。1. 初始化時設定許可數量,代表可用資源數;2. 線程調用acquire()獲取許可,若許可充足則繼續執行并減少計數器,否則阻塞等待;3. 線程完成任務后調用release()釋放許可,喚醒等待線程。公平性可通過構造函數設置,確保請求順序或允許插隊。使用示例中通過semaphore限制最多3個線程并發執行任務,模擬了數據庫連接池等場景。與鎖相比,semaphore更通用,支持多線程訪問而非僅單一線程。為避免死鎖,需注意獲取順序一致、設置超時機制及異常下資源釋放。應用場景包括流量控制、資源限制及有界隊列實現,幫助構建高效穩定的并發程序。
信號量在Java中主要用于控制對共享資源的并發訪問數量,就像交通信號燈控制道路上的車輛數量一樣,確保資源不會因為過度并發而崩潰。它通過維護一個許可計數器來實現這一點,線程必須先獲取許可才能訪問資源,訪問完畢釋放許可。
Semaphore限制并發數的原理
Semaphore的核心在于它的許可(permit)計數器。初始化時,你可以設置這個計數器的初始值,代表可用的許可數量。
立即學習“Java免費學習筆記(深入)”;
- 獲取許可(acquire): 當一個線程想要訪問共享資源時,它會嘗試調用acquire()方法來獲取一個許可。
- 如果許可計數器大于0,線程成功獲取許可,計數器減1。線程可以繼續執行。
- 如果許可計數器等于0,線程會被阻塞,直到有其他線程釋放許可。
- 釋放許可(release): 當線程完成對共享資源的訪問后,它會調用release()方法來釋放許可。
- 許可計數器加1。
- 如果有其他線程因為等待許可而被阻塞,那么其中一個線程會被喚醒,并獲取許可繼續執行。
Semaphore的公平性可以通過構造函數指定。公平信號量會按照線程請求許可的順序來分配許可,而非公平信號量則允許“插隊”,即如果一個線程恰好在許可可用時嘗試獲取,即使有其他線程在等待,它也可能先獲取到許可。
Java中如何使用Semaphore?
import java.util.concurrent.Semaphore; public class SemaphoreExample { private static final int MAX_PERMITS = 3; // 最大并發數 private static Semaphore semaphore = new Semaphore(MAX_PERMITS, true); // 公平鎖 public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(new Task(i)).start(); } } static class Task implements Runnable { private int taskId; public Task(int taskId) { this.taskId = taskId; } @Override public void run() { try { System.out.println("Thread " + taskId + " is waiting for permit."); semaphore.acquire(); System.out.println("Thread " + taskId + " acquired permit."); // 模擬耗時操作 Thread.sleep((long) (Math.random() * 1000)); System.out.println("Thread " + taskId + " is releasing permit."); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
在這個例子中,我們創建了一個最多允許3個線程同時訪問的信號量。每個線程在執行任務前都需要先獲取許可,執行完畢后釋放許可。
Semaphore與鎖(Lock)的區別是什么?
鎖(例如ReentrantLock)通常用于保護臨界區,確保同一時間只有一個線程可以訪問。Semaphore則更通用,它可以控制多個線程同時訪問共享資源的數量。鎖本質上是許可數量為1的信號量。
假設你有一個數據庫連接池,你希望限制同時連接到數據庫的線程數量,這時Semaphore就非常有用。而如果你只是想保護一個共享變量,防止并發修改,那么鎖可能更合適。
如何避免Semaphore的死鎖問題?
死鎖是并發編程中常見的問題,Semaphore也不例外。要避免死鎖,需要注意以下幾點:
- 避免循環等待: 線程獲取多個信號量的順序要一致。如果線程A先獲取信號量S1,再獲取S2,那么其他線程也應該遵循相同的順序。
- 設置超時時間: acquire()方法有帶超時時間的版本,例如acquire(long timeout, TimeUnit unit)。如果線程在指定時間內沒有獲取到許可,可以放棄等待,避免永久阻塞。
- 資源釋放: 確保在任何情況下,線程都能釋放已經獲取的信號量,即使發生異常。可以使用try-finally塊來保證釋放操作的執行。
try { semaphore.acquire(); // ... 執行操作 ... } catch (InterruptedException e) { // ... 處理中斷 ... } finally { semaphore.release(); }
Semaphore在實際開發中的應用場景有哪些?
除了數據庫連接池,Semaphore還可以用于:
總的來說,Semaphore是一個強大的并發控制工具,理解它的原理和使用方法,可以幫助你編寫更健壯、更高效的并發程序。當然,并發編程本身就比較復雜,需要仔細考慮各種邊界情況和潛在的問題。