java併發程式設計系列原理篇--JDK中的通訊工具類Semaphore

爪哇洋發表於2020-06-16

前言

java多執行緒之間進行通訊時,JDK主要提供了以下幾種通訊工具類。主要有Semaphore、CountDownLatch、CyclicBarrier、exchanger、Phaser這幾個通訊類。下面我們來詳細介紹每個工具類的作用、原理及用法。

Semaphore介紹

Semaphore翻譯過來是訊號的意思。顧名思義,這個工具類提供的功能就是多個執行緒彼此“打訊號”。而這個“訊號”是一個int型別的資料,也可以看成是一種“資源”,用來限定執行緒訪問該資源的數量。
它的建構函式有兩個,一個引數的用來指定執行緒訪問資源的數量;兩個引數的一個用來指定執行緒訪問資源的數量,一個用來指定是否為公平鎖。關於公平鎖非公平鎖的概念請參照文章java併發程式設計系列之原理篇-synchronized與鎖。建構函式程式碼如下:


  // 預設情況下使用非公平
  public Semaphore(int permits) {
        sync = new NonfairSync(permits);
  }
  public Semaphore(int permits, boolean fair) {
      sync = fair ? new FairSync(permits) : new NonfairSync(permits);
  }

它的最主要的兩個方法是acquire()和release()。acquire()方法會申請一個permit,而release方法會釋放一個permit。當然,你也可以申請多個acquire(int permits)或者釋放多個release(int permits)。每次acquire,permits就會減少一個或者多個。如果減少到了0,再有其他執行緒來acquire,那就要阻塞這個執行緒直到有其它執行緒release permit為止。

Semaphore的使用

Semaphore主要用來控制執行緒訪問資源的數量的場景。舉個例子,在併發條件下,我只想讓3個執行緒來執行某一任務。請看示例程式碼:

public class SemaphoreDemo {

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 10; i++) {
            new Thread(new MyThread(i,semaphore)).start();
        }
    }

     static class  MyThread implements Runnable{

        private int id;//執行緒的ID號
        private Semaphore semaphore;

        public MyThread(int id, Semaphore semaphore){
            this.id = id;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                //獲取訊號量permit許可
                semaphore.acquire();
                //接下來可以用來執行具體的執行緒任務
                System.out.println(String.format("當前的執行緒是%d,還剩有%d個執行緒資源可以使用,有%d個執行緒處於等待中。",
                        id,semaphore.availablePermits(),semaphore.getQueueLength()));
                Random random = new Random();
                //隨機睡眠時間,打亂釋放順序
                Thread.sleep(random.nextInt(1000));
                System.out.println(String.format("執行緒%d釋放了資源",id));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                //任務結束,釋放資源
                semaphore.release();
            }
        }
    }
}

輸出結果:

當前的執行緒是1,還剩有1個執行緒資源可以使用,有0個執行緒處於等待中。
當前的執行緒是0,還剩有2個執行緒資源可以使用,有0個執行緒處於等待中。
當前的執行緒是2,還剩有0個執行緒資源可以使用,有0個執行緒處於等待中。
執行緒2釋放了資源
當前的執行緒是3,還剩有0個執行緒資源可以使用,有6個執行緒處於等待中。
執行緒1釋放了資源
當前的執行緒是4,還剩有0個執行緒資源可以使用,有5個執行緒處於等待中。
執行緒0釋放了資源
當前的執行緒是5,還剩有0個執行緒資源可以使用,有4個執行緒處於等待中。
執行緒3釋放了資源
當前的執行緒是6,還剩有0個執行緒資源可以使用,有3個執行緒處於等待中。
執行緒4釋放了資源
當前的執行緒是7,還剩有0個執行緒資源可以使用,有2個執行緒處於等待中。
執行緒5釋放了資源
當前的執行緒是8,還剩有0個執行緒資源可以使用,有1個執行緒處於等待中。
執行緒8釋放了資源
當前的執行緒是9,還剩有0個執行緒資源可以使用,有0個執行緒處於等待中。
執行緒7釋放了資源
執行緒6釋放了資源
執行緒9釋放了資源

從結果可以看出來,最初搶到這3個資源的執行緒是1,0,2,而其他執行緒進入了等待佇列。之後每當有一個執行緒釋放了該資源,才會有其他在等待佇列的執行緒搶到資源。Semaphore預設的acquire方法是會讓執行緒進入等待佇列,且會丟擲中斷異常。但它還有一些方法可以忽略中斷或不進入阻塞佇列:

 // 忽略中斷
 public void acquireUninterruptibly()
 public void acquireUninterruptibly(int permits)

 // 不進入等待佇列,底層使用CAS
 public boolean tryAcquire
 public boolean tryAcquire(int permits)
 public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException
 public boolean tryAcquire(long timeout, TimeUnit unit)

Semaphore的原理

Semaphore內部有一個繼承了AQS的同步器Sync成員變數,重寫了tryAcquireShared方法。在這個方法裡,會去嘗試獲取資源。如果獲取失敗(想要的資源數量小於目前已有的資源數量),就會返回一個負數(代表嘗試獲取資源失敗)。然後當前執行緒就會進入AQS的等待佇列。具體的程式碼邏輯請檢視JDK1.8中java.util.concurrent包下的Semaphore類。

參考連結

在這裡很感謝能夠有幸看到來自各個大廠大神們的開源專案深入淺出Java多執行緒,讓我對多執行緒的知識有一個更深層次的瞭解。文中如有地方寫的不妥當或者有疑問,請大家留言,大家相互學習。感謝!

相關文章