Java并发编程实战————Semaphore信号量的使用浅析
引言
本篇博客講解《Java并發編程實戰》中的同步工具類:信號量?的使用和理解。
從概念、含義入手,突出重點,配以代碼實例及講解,并以生活中的案例做類比加強記憶。
什么是信號量
Java中的同步工具類信號量即計數信號量(Counting Semaphore),是用來控制訪問某個特定資源的操作數量,或同時執行某個指定操作的數量。可以簡單理解為信號量用來限制對某個資源的某種操作的數量。
一般用于實現某種資源池,或對容器施加邊界。
?信號量管理著一組有限個數的虛擬許可(permit),而許可的數量就是限制特定操作數量的關鍵。
信號量的使用
前面已經說過,信號量一般用于實現某種資源池或對容器施加邊界,這都是一個對特定操作的限制用途。那么想象一下,如何限制操作的數量,達到為一個再普通不過的容器施加邊界的效果呢?答案是給容器的某種操作(可以是添加或刪除元素,應該廣義的理解“某種操作”這個關鍵字眼)增加一道執行許可,只有在獲得許可的情況下才可以執行這個操作:
上圖左邊是普通的對容器的操作,右邊是有了信號量的對容器的操作。可以看出,在增加了中間的信號量之后,對容器的操作將會受限。
Semaphore
了解了信號量的大概含義,那么進一步深入到Java類庫的層面,JDK為開發者提供了java.util.concurrent包下的Semaphore類,它的含義就是上面所述的信號量,管理著一組permit。
以“為容器施加邊界”這一信號量用途為例。首先我們要明確一點,使用信號量的方式來實現施加邊界的方式,其針對的是操作而不是容器的容量!再一次重申,是限制了操作,而不是容器的容量!
強調限制操作,是為了要明白一點:使用信號量來施加邊界,必然會對這個容器的某些操作進一步封裝。比如添加方法,就會在調用add之前先行調用Semaphore對象的acquire()方法,在與這個操作相反的操作中去release()。并且,acquire()方法是阻塞式的,這就代表沒有閑置許可的時候,操作將會阻塞直到有許可被釋放。
下面代碼用信號量來對HashSet這個最普通的容器來施加一個添加限制,進一步封裝,使其成為一個有界的阻塞式的容器。
public class BoundedHashSet<T> {private final Set<T> set;private final Semaphore sem;public BoundedHashSet(int bound) {this.set = Collections.synchronizedSet(new HashSet<>());this.sem = new Semaphore(bound);}public boolean add(T o) throws InterruptedException {sem.acquire();boolean wasAdded = false;try {wasAdded = set.add(o);return wasAdded;} finally {if (!wasAdded)sem.release();}}public boolean remove(Object o) {boolean wasRemoved = set.remove(o);if (wasRemoved)sem.release();return wasRemoved;}/** 只是為了方便打印的 */public void print() {System.out.print(Thread.currentThread().getName() + " : ");this.set.forEach(o -> System.out.print(o + " "));}/** 用于測試的主方法 */public static void main(String[] args) {BoundedHashSet<String> names = new BoundedHashSet<>(5);new Thread(() -> {for (int i = 0; i < 100; i++) {try {names.add("name" + i);names.print();System.out.println();TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}}}, "TH-ADD").start();new Thread(() -> {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "--------執行清理,刪除name" + i);names.remove("name" + i);try {TimeUnit.SECONDS.sleep(10);} catch (Exception e) {e.printStackTrace();}}},"TH-REMOVE").start();} }執行結果如下:
我們用一個線程為這個“有界”容器每隔1秒鐘添加一個元素,然后另一個線程每隔10秒鐘移除一個元素。且初始化了這個容器的信號量為5,那么當容器中添加元素的數量達到5之后,5個許可全部被占用,添加操作將進入阻塞狀態,直到remove的時候釋放一個許可,才可以繼續添加元素。從上述結果可以看出兩點:
1、擁有5個許可的信號量成功的限制了容器的元素個數(即為容器施加了一個邊界);
2、添加的操作在沒有獲得許可的情況下將進入阻塞狀態,在執行的過程中也恰恰印證了這一點:當remove執行并release()之后,添加操作會立刻執行。
生活中的類比
其實這個類比博主認為,從嚴謹的角度來講,并不是完全符合信號量的概念,但是我們可以類比的同時找出不同點,不僅有效的通過生活案例理解了信號量,還對與之不同的地方增加了深刻的印象,所以還是決定拿出來供大家參考。
上過學的同學可能都知道,學校有獎學金制度。雖然我沒怎么得過獎學金,但是大概的邏輯還是比較好理解。
學校的獎學金制度是怎樣的呢?
學校每年都會給全校的學生指定數量的全額獎學金名額,比如全額獎學金5名。那么如果想獲得全額獎學金,就必須先獲得名額才行。
從這個簡單的邏輯我們可以找出關鍵的與信號量中的概念相匹配的內容:
獎學金 = 特定資源
獲得(獎學金) = 指定操作(如remove操作)
名額 = 一組定額許可的信號量
名額已滿,來年再報?= 操作阻塞,等待釋放許可
?有了上面的等式,信號量的神秘面紗就算徹底被我們揭開了,原來它就是一個管理一組定額許可的通行證,要想執行操作,那就必須先得到許可,否則就阻塞。
總結
信號量的概念:限制操作數量。
一個類:Semaphore ,兩個方法:acquire()、release()。
用途:對容器施加邊界,對容器的操作的再封裝。
另外,獎學金和信號量之間的類比并不完全匹配,不過這種程度的類比已經相當清晰,至于哪些信息有所差異,留給各位看官自己去挖掘。如果有什么新的發現,真誠希望在博客下方留言。
愿所有熱愛編程的開發者共同進步!
?
總結
以上是生活随笔為你收集整理的Java并发编程实战————Semaphore信号量的使用浅析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java8————Base64
- 下一篇: 发生在“注解”@的那些事儿