亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Java 并發工具之 Semaphore

1. 前言

從本節開始,我們學習新一章內容 —— 同步工具。
本節帶領大家認識第一個常用的 Java 并發工具類之 Semaphore。

本節先通過一個生活中的例子為大家通俗解釋一下什么是 Semaphore 信號量,接著介紹 Semaphore 工具類的最基本用法,有了這些基本認識之后,給出 Semaphore 工具最常用的場合說明,然后通過簡單的編碼實現文中提到的生活案例,讓大家有一個理性的認識,之后帶領大家熟悉 Semaphore 最常用的一些編程方法,最后通過同類工具的比較,進一步加深對 Semaphore 工具類的理解。

Semaphore 工具類本身使用很簡單,重點是對常用編程方法的準確理解。

當我們遇到各類需要做并發控制的場合時,怎么做到選取最合適的并發工具加以應用呢?唯有多加練習,不斷總結各種并發工具之間的區別,透徹理解各類工具的應用場合,才能做到游刃有余,手到擒來。

下面我們正式開始介紹吧。

2. 概念解釋

從 JDK1.5 開始提供,Java 官方就在 java.util.concurrent 并發包中提供了 Semaphore 工具類。

那什么是 “Semaphore” 呢?單詞 “Semaphore” 在計算機世界中被解釋為中文 “信號量” ,但更能表述其含義的叫法應該是 “許可證管理器”。不管叫什么中文名稱,它就是一種計數信號量,用于管理一組資源,給資源的使用者規定一個量從而控制同一時刻的使用者數目。

這樣的解釋是不是很抽象?沒關系,在此為大家舉一個生活中通俗的例子,讓大家先對 “信號量” 及其應用有一個感性的認識。

大家先觀察一下下面過閘機的圖例,回想一下我們平時過閘機的場景。
圖片描述

(圖片來自網絡,圖片版權歸作者所有)

比如上圖中過閘機就是信號量的基本運用。

上圖中的乘客就類比是我們程序里面的各類線程,閘機就類比是一類線程需要使用的資源,而信號量就是某一時刻可用的閘機數量。

當某個時刻有乘客需要使用閘機過站時,首先他需要找到一臺沒有人使用的閘機,現實中他通過眼睛觀察即可知道,在我們程序里面就是需要觀察信號量,看能不能申請到代表可用閘機的信號量,如果能則表示有空閑閘機 (資源) 可用,否則需要等待其他乘客使用完畢 (信號量釋放)后再使用。

概念我們已經了解了,那 Semaphore 工具類最基本的用法是怎樣的呢?別急,看下面。

3. 基本用法

// 首先創建 Semaphore 對象
Semaphore semaphore = new Semaphore();
// 在資源操作開始之前,先獲取資源的使用許可
semaphore.acquire();
...
// 在獲取到資源后,利用資源進行業務處理
...
// 在資源操作完畢之后,釋放資源的使用許可
semaphore.release();
...

是不是很簡單,那 Semaphore 信號量在我們日常實踐中,到底應該應用在哪些場合比較合適呢?下面我們給出最常用的場景說明。

4. 常用場景

Semaphore 經常用于限制同一時刻獲取某種資源的線程數量,最為典型的就是做流量控制。

比如 WEB 服務器處理能力有限,需要控制網絡請求接入的最大連接數,以防止過大的請求流量壓垮我們的服務器,導致整個應用不能正常提供服務。

比如數據庫服務器處理能力有限,需要控制數據庫最大連接數,以防止大量某個應用過分占有數據庫連接數,導致數據庫服務器不能為其他的應用提供足夠的連接請求。

當在研發過程中遇到類似這些場景時,就可以考慮直接應用 Semaphore 工具類輔助實現。

上面舉的生活中過閘機的例子,如果用程序表達,該如何實現呢?在程序中如何使用 Semaphore 信號量達到控制和應用呢?最直接方式就是去感受最簡單的例子,下面直接用最明了的代碼說明例子中如何應用了信號量。

5. 場景案例

import java.util.concurrent.Semaphore;

public class SemaphoreTest {

    // 先定義一個Semaphore信號量對象
    private static Semaphore semaphore = new Semaphore(3);

    // 測試方法
    public static void main(String[] args) {

        // 定義10個人過閘機
        for(int i=0; i<10; i++) {
            Person person = new Person(semaphore, i);
            new Thread(person).start();
        }
    }
}

在上面的代碼中,先創建了一個 Semaphore 信號量對象,然后賦給了每一位進站旅客 Person ,接下來每一位旅客如何動作呢,看下面的代碼。

import java.util.concurrent.Semaphore;

public class Person implements Runnable {

    private Semaphore semaphore;
    private String persionName;

    public Person(Semaphore semaphore, int persionNo) {
        this.semaphore = semaphore;
        this.persionName = "旅客" + persionNo;
    }

    public void run() {
        try {
            // 請求獲得信號量,就是請求(尋找)是否有可用的閘機
            semaphore.acquire();
            // 已經等到了可用閘機
            System.out.println(this.persionName + "已經占有一臺閘機");
            // 進站
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 已經進站
            System.out.println(this.persionName + "已經進站");
            // 讓出閘機給別人用
            semaphore.release();
        }
    }
}

在 Person 類中,首先通過 acquire 獲取了可用閘機,然后休眠 2 秒代表刷卡過閘機,最后在 finally 使用 release 方法讓出閘機。我們觀察一下運行結果。

旅客0已經占有一臺閘機  <---占據了一臺
旅客1已經占有一臺閘機  <---占據了一臺
旅客6已經占有一臺閘機  <---占據了一臺
旅客0已經進站         <---0號旅客已經進站釋放了閘機
旅客3已經占有一臺閘機  <---3號旅客這個時候才拿到了可用閘機
旅客1已經進站         <---1號旅客已經進站釋放了閘機
旅客2已經占有一臺閘機  <---2號旅客這個時候才拿到了可用閘機
旅客6已經進站         <---6號旅客已經進站釋放了閘機
旅客4已經占有一臺閘機  <---4號旅客這個時候才拿到了可用閘機
旅客3已經進站
旅客4已經進站
旅客5已經占有一臺閘機
旅客7已經占有一臺閘機
旅客2已經進站
旅客8已經占有一臺閘機
旅客7已經進站
旅客5已經進站
旅客9已經占有一臺閘機
旅客8已經進站
旅客9已經進站

觀察結果發現,同一時刻最多只能有 3 位旅客占用閘機進站,其他旅客需要等待其進站后讓出閘機才能刷卡進站。

至此,大家對信號量已經有了初步的理解,接下來我們繼續豐富對 Semaphore 工具類的認識。

6. 其他方法介紹

除過上面代碼中使用的最基本的 acquire 方法和 release 方法之外,我們還需要掌握其他幾個核心方法的使用。下面逐個介紹。

  1. Semaphore(int permits, boolean fair)

上面的例子中使用了 Semaphore (int permits) 構造方法。
此構造方法也是用于創建信號量對象,第二個參數表示創建的信號量是否秉持公平競爭特性。即對資源的申請使用嚴格按照申請的順序給予允許。

一般情況下,我們使用 Semaphore (int permits) 構造方法就可以了。

  1. availablePermits()

返回當前還可用的許可數,即還允許多少個線程進行使用資源。套用在上面的例子中,就是返回當前還有多少臺閘機空閑可用。

int availablePermits = semaphore.availablePermits();
System.out.println("當前可用閘機數" + availablePermits);

>>運行結果:
當前可用閘機數2
旅客0已經占有一臺閘機
當前可用閘機數1
旅客1已經占有一臺閘機
......
  1. hasQueuedThreads()

返回是否有線程正在等待獲取資源。也就是返回當前是否有人在排隊等待過閘機。

boolean hasQueuedThreads = semaphore.hasQueuedThreads();
System.out.println("當前是否有旅客等待閘機進站:"+hasQueuedThreads);

>>運行結果:
當前是否有旅客等待閘機進站:false
旅客0已經占有一臺閘機
當前是否有旅客等待閘機進站:false
旅客1已經占有一臺閘機
當前是否有旅客等待閘機進站:false
旅客2已經占有一臺閘機
  1. acquire(int permits)

申請指定數目的信號量許可,在獲取不到指定數目的許可時將一直阻塞。就好比一個旅客需要同時占用兩個閘機過站。類似的 release (int permits) 方法用于釋放指定數目的信號量許可。

acquire (int permits) 同上面例子中使用的 acquire () 最大的區別就是用于一次性申請多個許可,當參數 permits = 1 時,兩者相同。release (int permits) 和 release () 也是類似。

  1. tryAcquire()

嘗試申請信號量許可,無論是否申請成功都返回申請結果。當申請成功時返回 true , 否則返回 false 。程序里面根據申請結果決定后繼的處理流程。和 acquire () 的主要區別在于,不會阻塞立刻返回。

同類功能的方法還有 tryAcquire (int permits) 、tryAcquire (long timeout, TimeUnit unit) 、tryAcquire (int permits, long timeout, TimeUnit unit) 。這些方法實現的功能一樣,只是可以更加精細化地控制對資源申請,比如申請超時控制、申請許可數量。

7. 工具對比

大家可能有一個疑問了,Semaphore 好像和 synchronized 關鍵字沒什么區別,都可以實現同步。

其實不然,synchronized 真正用于并發控制,確保對某一個資源的串行訪問;而 Semaphore 限制訪問資源的線程數,其實并沒有實現同步,只有當 Semaphore 限制的資源同時只允許一個線程訪問時,兩者達到的效果一樣。

大家記住,Semaphore 和 synchronized 最主要的差別是 Semaphore 可以控制一個或多個并發,而 synchronized 只能是一個。這一點需要大家好好琢磨。

還是通過上面的例子的運行結果給大家做一下解釋。

>>運行結果:
旅客0已經占有一臺閘機  <-------
旅客1已經占有一臺閘機  | 觀察發現同時有多個并發執行,而非串行的一個旅客過完閘機后才輪到下一個旅客。
旅客2已經占有一臺閘機  <-------
......

8. 小結

本節通過一個簡單的例子,介紹了 Semaphore 的基本用法。另外對一些核心方法做了簡單介紹并給出應用場景。希望大家在學習過程中,多思考勤練習,早日掌握之。