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.
1.LocK情況
public class LockTest { public static void main(String[] args) { Lock lock=new ReentrantLock();
//Lock.lock() 注釋掉lock,就會報錯,因為沒有獲得鎖,就進行釋放 lock.unlock(); //Lock.unlock()之前,該線程必須事先持有這個鎖,否則拋出異常 }
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)
2.Semaphore情況
public class Semaphore { 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
結果說明了以下2個問題:
1.並沒有拋出異常,也就是線程在調用release()之前,並不要求先調用acquire() 。
2.我們看到可用的許可數目增加了一個,但我們的初衷是保證只有一個許可來達到互斥排他鎖的目的.
這就是 Semaphore的另一個用途: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,解決死鎖
// System.Out.PrintLn("===釋放信號=="); // semaphore1.release(); // semaphore2.release(); } }
輸出結果:
獲得Semaphore2
獲得Semaphore1
當我們打開最后的release代碼塊:
獲得Semaphore2 獲得Semaphore1 ===釋放信號== 獲得Semaphore1 獲得Semaphore2
所以:通過一個非owner的線程來實現死鎖恢復,但如果你使用的是Lock則做不到,可以把代碼中的兩個信號量換成兩個鎖對象試試。很明顯,前面也驗證過了,要使用Lock.unlock()來釋放鎖,首先你得擁有這個鎖對象,因此非owner線程(事先沒有擁有鎖)是無法去釋放別的線程的鎖對象。
==========================================================================
如果您覺得這篇文章對你有幫助,可以【關注我】或者【點贊】
希望我們一起在架構的路上,像鹿一樣追逐,也想鹿一樣優雅
==========================================================================