Java併發33:Semaphore基本方法與應用場景例項
本章主要對Semaphore進行學習。
1.Semaphore簡介
Semaphore,是JDK1.5的java.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併發之阻塞佇列LinkedBlockingQueue與ArrayBlockingQueue
關聯文章: 深入理解Java型別資訊(Class物件)與反射機制 深入理解Java列舉型別(enum) 深入理解Java註解型別(@Annotation) 深入理解Java類載入器(ClassLoader) 深入理解Java併發之synchronized實現原理
Java併發-ConcurrentModificationException原因原始碼分析與解決辦法
一、異常原因與異常原始碼分析 對集合(List、Set、Map)迭代時對其進行修改就會出現java.util.ConcurrentModificationException異常。這裡以ArrayList為例,例如下面的程式碼: ArrayList<String> list = new Arr