本博客系列是學習並發編程過程中的記錄總結。由於文章比較多,寫的時間也比較散,所以我整理了個目錄貼(傳送門),方便查閱。
Semaphore([' seməf :(r)])的主要作用是控制線程並發的數量。我們可以將Semaphore想象成景區的一個門衛,這個門衛負責發放景區入園的許可證。
景區為了游客的入園觀賞體驗,決定最多允許200個有個同時在園內觀賞。那么這個門衛在每天開園的時候手中都會有200張許可證,每當一個游客要入園的時候門衛會給游客發放一張許可證,當門衛手中的許可證發完之后再有游客需要入園的話就必須等待。
當游客觀賞完畢之后,出園的時候需要將許可證交還到門衛手上。門衛將這些交還的許可證再發等待的游客,這些游客就能順利入園了。
Semaphore的API簡介
Semaphore的API使用起來也比較簡單,常見的API簡介如下:
- Semaphore(int permits):構造方法,創建具有給定許可數的計數信號量並設置為非公平信號量。
- Semaphore(int permits,boolean fair):構造方法,當fair等於true時,創建具有給定許可數的計數信號量並設置為公平信號量。
- void acquire():從此信號量獲取一個許可前線程將一直阻塞。相當於一輛車占了一個車位。
- void acquire(int n):從此信號量獲取給定數目許可,在提供這些許可前一直將線程阻塞。比如n=2,就相當於一輛車占了兩個車位。
- boolean tryAcquire(int permits, long timeout, TimeUnit unit):嘗試獲取,在給定的時間內沒獲取到資源超時
- void release():釋放一個許可,將其返回給信號量。就如同車開走返回一個車位。
- void release(int n):釋放n個許可。
- int availablePermits():當前可用的許可數。
Semaphore的常見用法
下面給出一個Oracle官方文檔中的列子代碼:
class Pool {
// 可同時訪問資源的最大線程數
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
// 共享資源
protected Object[] items = new Object[MAX_AVAILABLE];
protected boolean[] used = new boolean[MAX_AVAILABLE];
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
private synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null;
}
private synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else
return false;
}
}
return false;
}
}
items數組可以看成是我們的共享資源,當有線程嘗試使用共享資源時,我們要求線程先獲得“許可”(調用Semaphore 的acquire方法),這樣線程就擁有了權限,否則就需要等待。當使用完資源后,線程需要調用Semaphore 的release方法釋放許可。
Semaphore並不能替代synchronized
有些書中提到:如果將Semaphore的許可證數量設置成1的話,就能實現synchronized的功能。其實這種說法是不對的。
下面使用Semaphore來控制對一個賬戶進行並發存錢和取錢的動作,如果Semaphore能實現synchronized的功能的話,賬戶最后的余額應該還是10000,但代碼執行后的結果並不是這樣。大家可以執行下面的代碼看下結果。
public static final int THREAD_COUNT = 100;
public static void main(String[] args) {
BankAccount myAccount = new BankAccount("accountOfMG", 10000.00);
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
int var = new Random().nextInt(100);
Thread.sleep(var);
} catch (InterruptedException e) {
e.printStackTrace();
}
double deposit = myAccount.deposit(1000.00);
System.out.println(Thread.currentThread().getName() + " balance1:" + deposit);
}
}).start();
}
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
int var = new Random().nextInt(100);
Thread.sleep(var);
} catch (InterruptedException e) {
e.printStackTrace();
}
double deposit = myAccount.withdraw(1000.00);
System.out.println(Thread.currentThread().getName() + " balance2:" + deposit);
}
}).start();
}
}
private static class BankAccount {
Semaphore semaphore = new Semaphore(1);
String accountName;
double balance;
public BankAccount(String accountName, double balance) {
this.accountName = accountName;
this.balance = balance;
}
public double deposit(double amount) {
try {
semaphore.acquire();
balance = balance + amount;
return balance;
} catch (Exception e) {
throw new RuntimeException("中斷...");
} finally {
semaphore.release();
}
}
public double withdraw(double amount) {
try {
semaphore.acquire();
balance = balance - amount;
return balance;
} catch (Exception e) {
throw new RuntimeException("中斷...");
} finally {
semaphore.release();
}
}
}
這里Semaphore並不能實現synchronized的功能的原因是:Semaphore並不能保證共享變量的可見性。
實現原理
Semaphore底層原理還是基於AQS機制的。這邊就不具體分析了,感興趣的可以參考我前面關於AQS的文章。
簡單總結
- Semaphore只能用來做線程同步——控制線程的執行順序,但是並不能保證線程安全;
- Semaphore主要用來控制線程的並發數量,通常用在限流組件中。
- Semaphore基於AQS機制實現。