1. 程式人生 > >Java併發33:Semaphore基本方法與應用場景例項

Java併發33:Semaphore基本方法與應用場景例項

本章主要對Semaphore進行學習。

1.Semaphore簡介

Semaphore,是JDK1.5java.util.concurrent併發包中提供的一個併發工具類。

所謂Semaphore訊號量 的意思。

這個叫法並不能很好地表示它的作用,更形象的說法應該是許可證管理器

其作用在JDK註釋中是這樣描述的:

A counting semaphore.
Conceptually, a semaphore maintains a set of permits.
Each {@link #acquire} blocks if necessary until a permit is available, and then takes it.
Each {@link #release} adds a permit, potentially releasing a blocking acquirer.
However, no actual permit objects are used; the {@code Semaphore} just keeps a count of the number available and acts accordingly.

翻譯過來,就是:

  • Semaphore是一個計數訊號量。
  • 從概念上將,Semaphore包含一組許可證
  • 如果有需要的話,每個acquire()方法都會阻塞,直到獲取一個可用的許可證
  • 每個release()方法都會釋放持有許可證的執行緒,並且歸還Semaphore一個可用的許可證
  • 然而,實際上並沒有真實的許可證物件供執行緒使用,Semaphore只是對可用的數量進行管理維護。

2.Semaphore方法說明

Semaphore的方法如下:

——Semaphore(permits)

初始化許可證數量的建構函式

——Semaphore(permits,fair)

初始化許可證數量

是否公平模式的建構函式

——isFair()

是否公平模式FIFO

——availablePermits()

獲取當前可用的許可證數量

——acquire()

當前執行緒嘗試去阻塞的獲取1個許可證。

此過程是阻塞的,它會一直等待許可證,直到發生以下任意一件事:

  • 當前執行緒獲取了1個可用的許可證,則會停止等待,繼續執行。
  • 當前執行緒被中斷,則會丟擲InterruptedException異常,並停止等待,繼續執行。

——acquire(permits)

當前執行緒嘗試去阻塞的獲取permits個許可證。

此過程是阻塞的,它會一直等待許可證,直到發生以下任意一件事:

  • 當前執行緒獲取了n個可用的許可證,則會停止等待,繼續執行。
  • 當前執行緒被中斷,則會丟擲InterruptedException異常,並停止等待,繼續執行。

——acquierUninterruptibly()

當前執行緒嘗試去阻塞的獲取1個許可證(不可中斷的)。

此過程是阻塞的,它會一直等待許可證,直到發生以下任意一件事:

  • 當前執行緒獲取了1個可用的許可證,則會停止等待,繼續執行。

——acquireUninterruptibly(permits)

當前執行緒嘗試去阻塞的獲取permits個許可證。

此過程是阻塞的,它會一直等待許可證,直到發生以下任意一件事:

  • 當前執行緒獲取了n個可用的許可證,則會停止等待,繼續執行。

——tryAcquire()

當前執行緒嘗試去獲取1個許可證。

此過程是非阻塞的,它只是在方法呼叫時進行一次嘗試。

如果當前執行緒獲取了1個可用的許可證,則會停止等待,繼續執行,並返回true。

如果當前執行緒沒有獲得這個許可證,也會停止等待,繼續執行,並返回false。

——tryAcquire(permits)

當前執行緒嘗試去獲取permits個許可證。

此過程是非阻塞的,它只是在方法呼叫時進行一次嘗試。

如果當前執行緒獲取了permits個可用的許可證,則會停止等待,繼續執行,並返回true。

如果當前執行緒沒有獲得permits個許可證,也會停止等待,繼續執行,並返回false。

——tryAcquire(timeout,TimeUnit)

當前執行緒在限定時間內,阻塞的嘗試去獲取1個許可證。

此過程是阻塞的,它會一直等待許可證,直到發生以下任意一件事:

  • 當前執行緒獲取了可用的許可證,則會停止等待,繼續執行,並返回true。
  • 當前執行緒等待時間timeout超時,則會停止等待,繼續執行,並返回false。
  • 當前執行緒在timeout時間內被中斷,則會丟擲InterruptedException一次,並停止等待,繼續執行。

——tryAcquire(permits,timeout,TimeUnit)

當前執行緒在限定時間內,阻塞的嘗試去獲取permits個許可證。

此過程是阻塞的,它會一直等待許可證,直到發生以下任意一件事:

  • 當前執行緒獲取了可用的permits個許可證,則會停止等待,繼續執行,並返回true。
  • 當前執行緒等待時間timeout超時,則會停止等待,繼續執行,並返回false。
  • 當前執行緒在timeout時間內被中斷,則會丟擲InterruptedException一次,並停止等待,繼續執行。

——release()

當前執行緒釋放1個可用的許可證。

——release(permits)

當前執行緒釋放permits個可用的許可證。

——drainPermits()

當前執行緒獲得剩餘的所有可用許可證。

——hasQueuedThreads()

判斷當前Semaphore物件上是否存在正在等待許可證的執行緒。

——getQueueLength()

獲取當前Semaphore物件上是正在等待許可證的執行緒數量。

3.Semaphore方法練習

練習目的:熟悉Semaphore的各類方法的用法。

例項程式碼:

//new Semaphore(permits):初始化許可證數量的建構函式
Semaphore semaphore = new Semaphore(5);

//new Semaphore(permits,fair):初始化許可證數量和是否公平模式的建構函式
semaphore = new Semaphore(5, true);

//isFair():是否公平模式FIFO
System.out.println("是否公平FIFO:" + semaphore.isFair());

//availablePermits():獲取當前可用的許可證數量
System.out.println("獲取當前可用的許可證數量:開始---" + semaphore.availablePermits());

//acquire():獲取1個許可證
//---此執行緒會一直阻塞,直到獲取這個許可證,或者被中斷(丟擲InterruptedException異常)。
semaphore.acquire();
System.out.println("獲取當前可用的許可證數量:acquire 1 個---" + semaphore.availablePermits());

//release():釋放1個許可證
semaphore.release();
System.out.println("獲取當前可用的許可證數量:release 1 個---" + semaphore.availablePermits());

//acquire(permits):獲取n個許可證
//---此執行緒會一直阻塞,直到獲取全部n個許可證,或者被中斷(丟擲InterruptedException異常)。
semaphore.acquire(2);
System.out.println("獲取當前可用的許可證數量:acquire 2 個---" + semaphore.availablePermits());

//release(permits):釋放n個許可證
semaphore.release(2);
System.out.println("獲取當前可用的許可證數量:release 1 個---" + semaphore.availablePermits());

//hasQueuedThreads():是否有正在等待許可證的執行緒
System.out.println("是否有正在等待許可證的執行緒:" + semaphore.hasQueuedThreads());

//getQueueLength():正在等待許可證的佇列長度(執行緒數量)
System.out.println("正在等待許可證的佇列長度(執行緒數量):" + semaphore.getQueueLength());

Thread.sleep(10);
System.out.println();
//定義final的訊號量
Semaphore finalSemaphore = semaphore;
new Thread(() -> {
    //drainPermits():獲取剩餘的所有的許可證
    int permits = finalSemaphore.drainPermits();
    System.out.println(Thread.currentThread().getName() + "獲取了剩餘的全部" + permits + "個許可證.");
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //釋放所有的許可證
    finalSemaphore.release(permits);
    System.out.println(Thread.currentThread().getName() + "釋放了" + permits + "個許可證.");
}).start();

Thread.sleep(10);
new Thread(() -> {
    try {
        //有一個執行緒正在等待獲取1個許可證
        finalSemaphore.acquire();
        System.out.println(Thread.currentThread().getName() + "獲取了1個許可證.");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //釋放1個許可證
    finalSemaphore.release();
    System.out.println(Thread.currentThread().getName() + "釋放了1個許可證.");

}).start();
Thread.sleep(10);
System.out.println();
System.out.println("獲取當前可用的許可證數量:drain 剩餘的---" + finalSemaphore.availablePermits());
System.out.println("是否有正在等待許可證的執行緒:" + finalSemaphore.hasQueuedThreads());
System.out.println("正在等待許可證的佇列長度(執行緒數量):" + finalSemaphore.getQueueLength());
System.out.println();

Thread.sleep(10);
new Thread(() -> {
    try {
        //有一個執行緒正在等待獲取2個許可證
        finalSemaphore.acquire(2);
        System.out.println(Thread.currentThread().getName() + "獲取了2個許可證.");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //釋放兩個許可證
    finalSemaphore.release(2);
    System.out.println(Thread.currentThread().getName() + "釋放了2個許可證.");
}).start();
Thread.sleep(10);
System.out.println();
System.out.println("獲取當前可用的許可證數量:drain 剩餘的---" + finalSemaphore.availablePermits());
System.out.println("是否有正在等待許可證的執行緒:" + finalSemaphore.hasQueuedThreads());
System.out.println("正在等待許可證的佇列長度(執行緒數量):" + finalSemaphore.getQueueLength());
System.out.println();

Thread.sleep(5000);
System.out.println();
System.out.println("獲取當前可用的許可證數量:---" + finalSemaphore.availablePermits());
System.out.println("是否有正在等待許可證的執行緒:" + finalSemaphore.hasQueuedThreads());
System.out.println("正在等待許可證的佇列長度(執行緒數量):" + finalSemaphore.getQueueLength());

執行結果:

是否公平FIFO:true
獲取當前可用的許可證數量:開始---5
獲取當前可用的許可證數量:acquire 1---4
獲取當前可用的許可證數量:release 1---5
獲取當前可用的許可證數量:acquire 2---3
獲取當前可用的許可證數量:release 1---5
是否有正在等待許可證的執行緒:false
正在等待許可證的佇列長度(執行緒數量):0

Thread-0獲取了剩餘的全部5個許可證.

獲取當前可用的許可證數量:drain 剩餘的---0
是否有正在等待許可證的執行緒:true
正在等待許可證的佇列長度(執行緒數量):1


獲取當前可用的許可證數量:drain 剩餘的---0
是否有正在等待許可證的執行緒:true
正在等待許可證的佇列長度(執行緒數量):2

Thread-0釋放了5個許可證.
Thread-2獲取了2個許可證.
Thread-1獲取了1個許可證.
Thread-1釋放了1個許可證.
Thread-2釋放了2個許可證.

獲取當前可用的許可證數量:---5
是否有正在等待許可證的執行緒:false
正在等待許可證的佇列長度(執行緒數量):0

4.Semaphore應用場景-例項

Semaphore經常用於限制獲取某種資源的執行緒數量。

場景說明:

  • 模擬學校食堂的視窗打飯過程
  • 學校食堂有2個打飯視窗
  • 學校中午有20個學生 按次序 排隊打飯
  • 每個人打飯時耗費時間不一樣
  • 有的學生耐心很好,他們會一直等待直到打到飯
  • 有的學生耐心不好,他們等待時間超過了心裡預期,就不再排隊,而是回宿舍吃泡麵了
  • 有的學生耐心很好,但是突然接到通知,說是全班聚餐,所以也不用再排隊,而是去吃大餐了

重點分析

  • 食堂有2個打飯視窗:需要定義一個permits=2的Semaphore物件。
  • 學生 按次序 排隊打飯:此Semaphore物件是公平的。
  • 有20個學生:定義20個學生執行緒。
  • 打到飯的學生:呼叫了acquireUninterruptibly()方法,無法被中斷
  • 吃泡麵的學生:呼叫了tryAcquire(timeout,TimeUnit)方法,並且等待時間超時了
  • 吃大餐的學生:呼叫了acquire()方法,並且被中斷了

例項程式碼:

定義2個視窗的食堂

/**
 * 打飯視窗
 * 2:   2個打飯視窗
 * true:公平佇列-FIFO
 */
static Semaphore semaphore = new Semaphore(2, true);

定義打飯學生

/**
 * <p>打飯學生</p>
 *
 * @author hanchao 2018/3/31 19:45
 **/
static class Student implements Runnable {
    private static final Logger LOGGER = Logger.getLogger(Student.class);
    //學生姓名
    private String name;
    //打飯許可
    private Semaphore semaphore;
    /**
     * 打飯方式
     * 0    一直等待直到打到飯
     * 1    等了一會不耐煩了,回宿舍吃泡麵了
     * 2    打飯中途被其他同學叫走了,不再等待
     */
    private int type;

    public Student(String name, Semaphore semaphore, int type) {
        this.name = name;
        this.semaphore = semaphore;
        this.type = type;
    }

    /**
     * <p>打飯</p>
     *
     * @author hanchao 2018/3/31 19:49
     **/
    @Override
    public void run() {
        //根據打飯情形分別進行不同的處理
        switch (type) {
            //打飯時間
            //這個學生很有耐心,它會一直排隊直到打到飯
            case 0:
                //排隊
                semaphore.acquireUninterruptibly();
                //進行打飯
                try {
                    Thread.sleep(RandomUtils.nextLong(1000, 3000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //將打飯機會讓後後面的同學
                semaphore.release();
                //打到了飯
                LOGGER.info(name + " 終於打到了飯.");
                break;

            //這個學生沒有耐心,等了1000毫秒沒打到飯,就回宿舍泡麵了
            case 1:
                //排隊
                try {
                    //如果等待超時,則不再等待,回宿舍吃泡麵
                    if (semaphore.tryAcquire(RandomUtils.nextInt(6000, 16000), TimeUnit.MILLISECONDS)) {
                        //進行打飯
                        try {
                            Thread.sleep(RandomUtils.nextLong(1000, 3000));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //將打飯機會讓後後面的同學
                        semaphore.release();
                        //打到了飯
                        LOGGER.info(name + " 終於打到了飯.");
                    } else {
                        //回宿舍吃泡麵
                        LOGGER.info(name + " 回宿舍吃泡麵.");
                    }
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                }
                break;

            //這個學生也很有耐心,但是他們班突然宣佈聚餐,它只能放棄打飯了
            case 2:
                //排隊
                try {
                    semaphore.acquire();
                    //進行打飯
                    try {
                        Thread.sleep(RandomUtils.nextLong(1000, 3000));
                    } catch (InterruptedException e) {
                        //e.printStackTrace();
                    }
                    //將打飯機會讓後後面的同學
                    semaphore.release();
                    //打到了飯
                    LOGGER.info(name + " 終於打到了飯.");
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    //被叫去聚餐,不再打飯
                    LOGGER.info(name + " 全部聚餐,不再打飯.");
                }
                break;
            default:
                break;
        }
    }
}

編寫食堂打飯過程:

/**
* <p>食堂打飯</p>
 *
 * @author hanchao 2018/3/31 21:13
 **/
public static void main(String[] args) throws InterruptedException {
    //101班的學生
    Thread[] students101 = new Thread[5];
    for (int i = 0; i < 20; i++) {
        //前10個同學都在耐心的等待打飯
        if (i < 10) {
            new Thread(new Student("打飯學生" + i, SemaphoreDemo.semaphore, 0)).start();
        } else if (i >= 10 && i < 15) {//這5個學生沒有耐心打飯,只會等1000毫秒
            new Thread(new Student("泡麵學生" + i, SemaphoreDemo.semaphore, 1)).start();
        } else {//這5個學生沒有耐心打飯
            students101[i - 15] = new Thread(new Student("聚餐學生" + i, SemaphoreDemo.semaphore, 2));
            students101[i - 15].start();
        }
    }
    //
    Thread.sleep(5000);
    for (int i = 0; i < 5; i++) {
        students101[i].interrupt();
    }
}

執行結果:

2018-04-01 21:13:16 INFO - 打飯學生1 終於打到了飯.
2018-04-01 21:13:16 INFO - 打飯學生0 終於打到了飯.
2018-04-01 21:13:18 INFO - 打飯學生2 終於打到了飯.
2018-04-01 21:13:18 INFO - 打飯學生3 終於打到了飯.
2018-04-01 21:13:19 INFO - 聚餐學生15 全部聚餐,不再打飯.
2018-04-01 21:13:19 INFO - 聚餐學生19 全部聚餐,不再打飯.
2018-04-01 21:13:19 INFO - 聚餐學生17 全部聚餐,不再打飯.
2018-04-01 21:13:19 INFO - 聚餐學生18 全部聚餐,不再打飯.
2018-04-01 21:13:19 INFO - 聚餐學生16 全部聚餐,不再打飯.
2018-04-01 21:13:19 INFO - 打飯學生4 終於打到了飯.
2018-04-01 21:13:20 INFO - 打飯學生5 終於打到了飯.
2018-04-01 21:13:21 INFO - 泡麵學生13 回宿舍吃泡麵.
2018-04-01 21:13:21 INFO - 泡麵學生11 回宿舍吃泡麵.
2018-04-01 21:13:21 INFO - 打飯學生7 終於打到了飯.
2018-04-01 21:13:22 INFO - 打飯學生6 終於打到了飯.
2018-04-01 21:13:23 INFO - 打飯學生9 終於打到了飯.
2018-04-01 21:13:24 INFO - 打飯學生8 終於打到了飯.
2018-04-01 21:13:24 INFO - 泡麵學生10 終於打到了飯.
2018-04-01 21:13:26 INFO - 泡麵學生14 終於打到了飯.
2018-04-01 21:13:26 INFO - 泡麵學生12 終於打到了飯.

相關推薦

Java併發33:Semaphore基本方法應用場景例項

本章主要對Semaphore進行學習。 1.Semaphore簡介 Semaphore,是JDK1.5的java.util.concurrent併發包中提供的一個併發工具類。 所謂Semaphore即 訊號量 的意思。 這個叫法並不能很好地表

Zookeeper基本原理應用場景

        Zookeeper是層次化的目錄結構,命名符合常規檔案系統規範。每個節點在zookeeper中叫做znode,並且其有一個唯一的路徑標識。節點Znode可以包含資料和子節點(EPHEMERAL型別的節點不能有子節點)。Znode中的資料可以有多個版本,比如某一個路徑下存有多個數據版本,那麼查詢

VUE 配置和基本方法應用

12px 商品列表 sets config err emp har 別名 單獨 個人寫的一個框架 $ npm install learnvue 粗略的介紹一下這個項目 進入 learnve文件 執行 $ npm install $ npm start 項目

Java 多線程 sleep()方法wait()方法的區別

程序 一段 exc 非靜態方法 not mil java程序 div 推薦   sleep()方法會使線程暫停執行一段時間,wait()方法會阻塞線程,直到被喚醒或等待時間超時。   兩者區別具體如下:   1 原理不同   sleep()方法是Thread類的靜態方法,使

Java 多線程 sleep()方法yield()方法的區別

就是 有關 方法 沒有 區別 sof interrupt 重新 線程   sleep()方法與yield()方法的區別如下:   1 是否考慮線程的優先級不同   sleep()方法給其他線程運行機會時不考慮線程的優先級,也就是說,它會給低優先級的線程運行的機會。而yiel

Nginx基本配置應用

sed stat 3.3 star pes nlp lin 查看 mon 一、準備 1.1 環境準備 CentOS7軟件環境 1.2 tomcat多實例 把/etc/profile.d/tomcat.sh中的變量註釋了 #export TOMCAT_HOME=/usr/lo

hive基本操作應用

nbsp ima doc 統計 info inf 文檔 http hadoop 通過hadoop上的hive完成WordCount 啟動hadoop Hdfs上創建文件夾 上傳文件至hdfs 啟動Hive 創建原始文檔表 導入文件內容到表docs並查看 用

hive的基本操作應用

AI text -a SM 創建文件夾 con 結果 基本 input 1.啟動hadoop 2.Hdfs上創建文件夾 創建的文件夾是datainput 3.上傳文件至hdfs 啟動Hive 4。創建原始文檔表 5.導入文件內容到表docs並

關於一些Java基礎數據類型的常用方法應用場景的小思考

get light || 成了 一半 ava 類型 這樣的 style 昨天遇到一個問題,按照我的一半解決方法是傳一個參數,然後通過參數來控制邏輯處理;但是領導發現String的一個方法也可以完全完成該問題!而我完全沒有get到這個點! so,我認識到了自己的知識盲區;基礎

JAVA併發程式設計之基本概念

1、鎖是對物件訪問的時候,通過對物件加鎖,防止並行訪問的控制手段;對物件加鎖成功,代表我持有這個物件的監視器,解鎖,代表釋放了這個物件的監視器。 拿到物件的監視器,肯定是對物件加鎖成功的;對物件加鎖成功 ,程式可以主動Watiing或者Time_waiting在物件監視器上。 2、鎖與監

PDM技術的基本功能應用

  產品資料管理(Product Data Management,PDM)是當前國際上計算機應用領域的熱門技術,本人在中已經闡述了這項技術。雖然我國對PDM技術的應用剛剛起步,但由於該項技術的強大功能與對企業產品的設計、製造及維護提供了有力的資料資訊支援,使得PDM

JAVA併發程式設計:悲觀鎖樂觀鎖

生活 晴。 悲觀與樂觀的情緒概念 本篇來了解一下悲觀鎖和樂觀鎖,在瞭解這兩個鎖之前,我們首先有必要把悲觀和樂觀這兩個詞搞清楚: 悲觀:對世事懷有消極的看法,認為事物總往糟糕的方向發展。 樂觀:對世事懷有積極的態度,認為事物總往好的方向發展。 何為悲觀鎖 悲觀鎖: 假定會發

java Object中的基本方法

    在java object預設的基本方法中,主要包含如下方法:     getClass(), hashCode(), equals(), clone(), toString(), notify(), notifyAll(), &nbs

JAVA併發容器:JDK1.7 1.8 ConcurrentHashMap 區別

生活 為什麼我們總是沒有時間把事情做對,卻有時間做完它? 瞭解ConcurrentHashMap 工作中常用到hashMap,但是HashMap在多執行緒高併發場景下並不是執行緒安全的。 所以引入了ConcurrentHashMap,它是HashMap的執行緒安全版本,採用了分段

Java傳送郵件的基本配置步驟

Java傳送郵件的基本配置與步驟 java 這裡簡單介紹一種利用Java來發送郵件的方法。 Maven的POM.xml檔案載入jar包 <dependency> <gro

Java併發程式設計-Semaphore

  基於AQS的前世今生,來學習併發工具類Semaphore。本文將從Semaphore的應用場景、原始碼原理解析來學習這個併發工具類。 1、 應用場景   Semaphore用來控制同時訪問某個特定資源的運算元量,或者同時執行某個指定操作的數量。還可以用來實現某種資源池限制,或者對容器施加邊界。 1.

Java併發程式設計 之 同步佇列等待佇列

在上一篇部落格中,我簡單的介紹了對Condition和ReentrantLock的使用,但是想要更好的掌握多執行緒程式設計,單單會用是不夠的。這篇我會針對Condition方法中的await和signal的實現原理來梳理一下我的理解。 首先我們需要了解同步佇列和等待佇列的概念。簡單的

伸展樹的基本操作應用

      【總結】 由上面的分析介紹,我們可以發現伸展樹有以下幾個優點: (1)時間複雜度低,伸展樹的各種基本操作的平攤複雜度都是 O(log n)的。在樹狀資料結構中,無疑是非常優秀的。 (2)空間要求不高。與紅黑樹需

深入剖析java併發之阻塞佇列LinkedBlockingQueueArrayBlockingQueue

關聯文章: 深入理解Java型別資訊(Class物件)與反射機制 深入理解Java列舉型別(enum) 深入理解Java註解型別(@Annotation) 深入理解Java類載入器(ClassLoader) 深入理解Java併發之synchronized實現原理

Java併發-ConcurrentModificationException原因原始碼分析解決辦法

一、異常原因與異常原始碼分析   對集合(List、Set、Map)迭代時對其進行修改就會出現java.util.ConcurrentModificationException異常。這裡以ArrayList為例,例如下面的程式碼: ArrayList<String> list = new Arr