java併發編程系列原理篇–JDK中的通信工具類Semaphore

前言

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多線程,讓我對多線程的知識有一個更深層次的了解。文中如有地方寫的不妥當或者有疑問,請大家留言,大家相互學習。感謝!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

您可能也會喜歡…