Java提供了一個類Semaphore來實現信號量,概念上講,一個信號量相當於持有一些許可(permits),線程可以調用Semaphore對象的acquire()方法獲取一個許可,調用release()來歸還一個許可
1 構造方法:
Semaphore有兩個構造方法 Semaphore(int)
、Semaphore(int,boolean)
,參數中的int表示該信號量擁有的許可數量,boolean表示獲取許可的時候是否是公平的,如果是公平的那麼,當有多個線程要獲取許可時,會按照線程來的先後順序分配許可,否則,線程獲得許可的順序是不定的。這裏在jdk中講到 “一般而言,非公平時候的吞吐量要高於公平鎖”,這是爲什麼呢?附上鍊接中的一段話:
非公平鎖性能高於公平鎖性能的原因:在恢復一個被掛起的線程與該線程真正運行之間存在着嚴重的延遲。假設線程A持有一個鎖,並且線程B請求這個鎖。由於鎖被A持有,因此B將被掛起。當A釋放鎖時,B將被喚醒,因此B會再次嘗試獲取這個鎖。與此同時,如果線程C也請求這個鎖,那麼C很可能會在B被完全喚醒之前獲得、使用以及釋放這個鎖。這樣就是一種雙贏的局面:B獲得鎖的時刻並沒有推遲,C更早的獲得了鎖,並且吞吐量也提高了。當持有鎖的時間相對較長或者請求鎖的平均時間間隔較長,應該使用公平鎖。在這些情況下,插隊帶來的吞吐量提升(當鎖處於可用狀態時,線程卻還處於被喚醒的過程中)可能不會出現。
2 獲取許可
可以使用acquire()、acquire(int)、tryAcquire()
等去獲取許可,其中int參數表示一次性要獲取幾個許可,默認爲1個,acquire方法在沒有許可的情況下,要獲取許可的線程會阻塞,而tryAcquire()方法在沒有許可的情況下會立即返回 false,要獲取許可的線程不會阻塞,這與Lock類的lock()與tryLock()類似
3 釋放許可
線程可調用 release()、release(int)來釋放(歸還)許可,注意一個線程調用release()之前並不要求一定要調用了acquire (There is no requirement that a thread that releases a permit must have acquired that permit by calling {@link #acquire})
4 使用場景
我們一般使用信號量來限制訪問資源的線程數量,比如有一個食堂,最多允許5個人同時喫飯,則如下:
-
class EatThread extends Thread{
-
private Semaphore semaphore;
-
public EatThread(Semaphore semaphore){
-
this.semaphore=semaphore;
-
}
-
-
public void run(){
-
try {
-
semaphore.acquire();//獲取一個許可,當然也可以調用acquire(int),這樣一個線程就能拿到多個許可
-
long eatTime=(long) (Math.random()*10);
-
System.out.println(Thread.currentThread().getId()+" 正在喫飯");
-
TimeUnit.SECONDS.sleep(eatTime);
-
System.out.println(Thread.currentThread().getId()+" 已經喫完");
-
semaphore.release();//歸還許可
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
public class SemaphoreTest {
-
public static void main(String[] args) {
-
Semaphore semaphore=new Semaphore(5);//總共有5個許可
-
for(int i=0;i<7;i++){//定義七個喫的線程
-
new EatThread(semaphore).start();
-
}
-
}
-
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
結果如下:
-
9 正在喫飯
-
15 正在喫飯
-
13 正在喫飯
-
13 已經喫完
-
11 正在喫飯
-
10 正在喫飯
-
10 已經喫完
-
12 正在喫飯
-
14 正在喫飯
-
11 已經喫完
-
15 已經喫完
-
14 已經喫完
-
9 已經喫完
-
12 已經喫完
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
當我們在構造Semaphore對象時,如果設置的許可數量爲1,這時便會達到一個互斥排他鎖的效果,只有一個許可,有一個線程獲取了這個許可,那麼其他線程只有等待這個線程歸還了許可才能獲取到許可,當將Semaphore用作互斥排他鎖的作用時,要注意:
A semaphore initialized to one, and which is used such that it only has at most one permit available, can serve as a mutual exclusion lock. This is more commonly known as a binary semaphore, because it only has two states: one permit available, or zero permits available. When used in this way, the binary semaphore has the property (unlike many Lock implementations), that the “lock” can be released by a thread other than the owner (as semaphores have no notion of ownership). This can be useful in some specialized contexts, such as deadlock recovery.
文檔中提到,Semaphore與jdk中的Lock
的區別是
1. 使用Lock.unlock()之前,該線程必須事先持有這個鎖(通過Lock.lock()獲取),如下:
-
public class LockTest {
-
public static void main(String[] args) {
-
Lock lock=new ReentrantLock();
-
lock.unlock();
-
}
-
}
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
則會拋出異常,因爲該線程事先並沒有獲取lock對象的鎖:
-
Exception in thread "main" java.lang.IllegalMonitorStateException
-
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
-
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
-
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
-
at LockTest.main(LockTest.java:12)
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
對於Semaphore來講,如下:
-
public class SemaphoreTest {
-
public static void main(String[] args) {
-
Semaphore semaphore=new Semaphore(1);//總共有1個許可
-
System.out.println("可用的許可數目爲:"+semaphore.availablePermits());
-
semaphore.release();
-
System.out.println("可用的許可數目爲:"+semaphore.availablePermits());
-
}
-
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
結果如下:
-
可用的許可數目爲:1
-
可用的許可數目爲:2
- 1
- 2
- 1
- 2
i. 並沒有拋出異常,也就是線程在調用release()之前並不要求先調用acquire()
ii. 我們看到可用的許可數目增加了一個,但我們的初衷是保證只有一個許可來達到互斥排他鎖的目的,所以這裏要注意一下
2 Semaphore(1)可以做到一個deadlock recovery,我們來看下面一個例子
-
class WorkThread2 extends Thread{
-
private Semaphore semaphore1,semaphore2;
-
public WorkThread2(Semaphore semaphore1,Semaphore semaphore2){
-
this.semaphore1=semaphore1;
-
this.semaphore2=semaphore2;
-
}
-
public void releaseSemaphore2(){
-
System.out.println(Thread.currentThread().getId()+" 釋放Semaphore2");
-
semaphore2.release();
-
}
-
public void run() {
-
try {
-
semaphore1.acquire(); //先獲取Semaphore1
-
System.out.println(Thread.currentThread().getId()+" 獲得Semaphore1");
-
TimeUnit.SECONDS.sleep(5); //等待5秒讓WorkThread1先獲得Semaphore2
-
semaphore2.acquire();//獲取Semaphore2
-
System.out.println(Thread.currentThread().getId()+" 獲得Semaphore2");
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
class WorkThread1 extends Thread{
-
private Semaphore semaphore1,semaphore2;
-
public WorkThread1(Semaphore semaphore1,Semaphore semaphore2){
-
this.semaphore1=semaphore1;
-
this.semaphore2=semaphore2;
-
}
-
public void run() {
-
try {
-
semaphore2.acquire();//先獲取Semaphore2
-
System.out.println(Thread.currentThread().getId()+" 獲得Semaphore2");
-
TimeUnit.SECONDS.sleep(5);//等待5秒,讓WorkThread1先獲得Semaphore1
-
semaphore1.acquire();//獲取Semaphore1
-
System.out.println(Thread.currentThread().getId()+" 獲得Semaphore1");
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
public class SemphoreTest {
-
public static void main(String[] args) throws InterruptedException {
-
Semaphore semaphore1=new Semaphore(1);
-
Semaphore semaphore2=new Semaphore(1);
-
new WorkThread1(semaphore1, semaphore2).start();
-
new WorkThread2(semaphore1, semaphore2).start();
-
//此時已經陷入了死鎖,WorkThread1持有semaphore1的許可,請求semaphore2的許可
-
// WorkThread2持有semaphore2的許可,請求semaphore1的許可
-
// TimeUnit.SECONDS.sleep(10);
-
// //在主線程是否semaphore1,semaphore2,解決死鎖
-
// semaphore1.release();
-
// semaphore2.release();
-
}
-
-
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
在註釋最後面幾行代碼的情況下,結果爲,陷入了一個死鎖:
-
9 獲得Semaphore2
-
10 獲得Semaphore1
- 1
- 2
- 1
- 2
把註釋刪除,即在主線程釋放Semaphore,這樣就能解決死鎖:
-
9 獲得Semaphore2
-
10 獲得Semaphore1
-
9 獲得Semaphore1
-
10 獲得Semaphore2
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
這即符合文檔中說的,通過一個非owner的線程來實現死鎖恢復,但如果你使用的是Lock則做不到,可以把代碼中的兩個信號量換成兩個鎖對象試試。很明顯,前面也驗證過了,要使用Lock.unlock()來釋放鎖,首先你得擁有這個鎖對象,因此非owner線程(事先沒有擁有鎖)是無法去釋放別的線程的鎖對象