並發編程中:Semaphore信號量與lock的區別


Semaphore,信號量,常用於限制可以訪問某些資源的線程數量,比如連接池、對象池、線程池等等。其中,你可能最熟悉數據庫連接池,在同一時刻,一定是允許多個線程同時使用連接池的,當然,每個連接在被釋放前,是不允許其他線程使用的。

信號量實現了一個最簡單的互斥鎖功能。估計你會覺得奇怪,既然有 Java SDK 里面提供了 Lock,為啥還要提供一個 Semaphore ?其實實現一個互斥鎖,僅僅是 Semaphore 的部分功能,Semaphore 還有一個功能是 Lock 不容易實現的,那就是: 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線程(事先沒有擁有鎖)是無法去釋放別的線程的鎖對象。

 

==========================================================================

  如果您覺得這篇文章對你有幫助,可以【關注我】或者【點贊

  希望我們一起在架構的路上,像鹿一樣追逐,也想鹿一樣優雅

==========================================================================

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM