第一篇文章將介紹信號量-特別是對信號量進行計數 。 信號量是用于限制對資源訪問的經常被誤解和使用不足的工具。 對于其他控制對資源的訪問的方式,它們將被忽略。 但是信號量為我們提供了一個超越常規同步和其他工具所能提供的工具集的工具集。
那么什么是信號量? 想到信號量的最簡單方法是將其視為允許n個單位被獲取并提供獲取和釋放機制的抽象。 它安全地允許我們確保在給定的時間只有n個進程可以訪問特定資源 。
一切都很好,但是這將達到什么目的呢? 好吧,這是一個示例,將有助于解釋其用法。 它使用位于1.5。中的java.util.concurrent包中精心設計的Semaphore類。
限制連接
也許我們有一個過程可以通過HTTP定期為我們下載資源。 我們不想向任何主機發送垃圾郵件,同時,我們想限制正在建立的連接數,因此我們不會耗盡允許的有限文件句柄或出站連接。 一種簡單的方法是使用信號量:
public class ConnectionLimiter {private final Semaphore semaphore;private ConnectionLimiter(int maxConcurrentRequests) {semaphore = new Semaphore(maxConcurrentRequests);}public URLConnection acquire(URL url) throws InterruptedException,IOException {semaphore.acquire();return url.openConnection();}public void release(URLConnection conn) {try {/** ... clean up here*/} finally {semaphore.release();}}
}
對于資源有限的問題,這是一個很好的解決方案。 對acquire()的調用將阻塞,直到獲得許可為止。 信號燈的優點在于,它隱藏了管理訪問控制,計算許可數以及確保正確的線程安全性的所有復雜性。
危險性
與大多數鎖定或同步方法一樣,存在一些潛在問題。
要記住的第一件事是, 始終釋放您獲得的東西 。 這是通過使用try..finally構造完成的。
使用信號量時,還有其他不太明顯的問題可能會降臨您。 以下課程顯示了死鎖,只有您中最幸運的人才能避免。 您會注意到,獲得兩個信號量許可的兩個線程的執行順序相反。 (為簡潔起見,try..finally最終被省去了)。
public static void main(String[] args) throws Exception {Semaphore s1 = new Semaphore(1);Semaphore s2 = new Semaphore(1);Thread t = new Thread(new DoubleResourceGrabber(s1, s2));// now reverse them ... here comes trouble!Thread t2 = new Thread(new DoubleResourceGrabber(s2, s1));t.start();t2.start();t.join();t2.join();System.out.println("We got lucky!");
}private static class DoubleResourceGrabber implements Runnable {private Semaphore first;private Semaphore second;public DoubleResourceGrabber(Semaphore s1, Semaphore s2) {first = s1;second = s2;}public void run() {try {Thread t = Thread.currentThread();first.acquire();System.out.println(t + " acquired " + first);Thread.sleep(200); // demonstrate deadlocksecond.acquire();System.out.println(t + " acquired " + second);second.release();System.out.println(t + " released " + second);first.release();System.out.println(t + " released " + first);} catch (InterruptedException ex) {ex.printStackTrace();}}
}
如果運行此程序,則很有可能會掛起一個進程。 鎖排序的問題與Java中的常規互斥鎖或同步一樣,也適用于信號量。 在某些情況下,超時(請參閱本文后面的tryAcquire()注釋)可用于防止死鎖導致進程掛起,但是死鎖通常是可以避免的邏輯錯誤的征兆。 如果您不熟悉死鎖,建議您仔細閱讀它們。 維基百科上有一篇關于死鎖的文章,該文章同樣適用于所有語言。
使用信號量(包括二進制信號量,即互斥體)時應注意的主要事項是:
- 獲取后不釋放(丟失的釋放調用或引發異常并且沒有finally塊)
- 長時間保持信號量,導致線程饑餓
- 死鎖(如上所示)
有用的信號燈技巧
Java中Semaphores的一個有趣的特性是, 不必通過與Acquisition相同的線程來調用release 。 這意味著您可以具有一個線程限制器,該線程限制器可以通過調用acquire()來基于信號量池或創建線程。 然后,正在運行的線程可以在完成時釋放其自己的信號燈許可。 這是Java中普通互斥鎖所沒有的有用屬性。
另一個技巧是在運行時增加許可數量 。 與您可能會猜到的相反,信號量中的許可數量不是固定的,并且即使未進行相應的acquire()調用,對release()的調用也會始終增加許可的數量。 請注意,如果在沒有進行acquire()的情況下錯誤地調用release() ,這也會導致錯誤。
最后,在Java的Semaphore中有一些有用的方法要熟悉。 方法AcquireInterruptible()將獲取資源,如果資源被中斷,則重新嘗試。 這意味著沒有對InterruptedException的外部處理。 tryAcquire()方法允許我們限制等待許可的時間-我們可以在沒有許可的情況下立即返回,也可以等待指定的超時時間。 如果您以某種方式知道了無法輕松修復或跟蹤的死鎖,則可以通過使用帶有適當超時的tryAcquire()來幫助防止鎖定進程。
用途
計數信號量有哪些可能的用途? 請注意以下幾點:
- 限制對磁盤的并發訪問(由于競爭磁盤搜尋,這可能會降低性能)
- 線程創建限制
- JDBC連接池/限制
- 網絡連接限制
- 限制CPU或內存密集型任務
當然,信號量是訪問控制和同步的一個很底層的構建塊。 Java為我們提供了Java 1.5及更高版本中引入的大量并發機制和策略。 在接下來的文章中,我們將介紹一些更抽象的并發管理方法,包括執行器,BlockingQueues,Barriers,Future以及一些新的并發Collection類。
您發現信號量有什么用途? 通過發表評論讓我們知道–我們喜歡語音軟件。
參考: Java并發第1部分–來自我們的JCG合作伙伴的信號燈 ,在Carfey Software博客上 。
- Java并發教程–重入鎖
- Java并發教程–線程池
- Java并發教程–可調用,將來
- Java并發教程–阻塞隊列
- Java并發教程– CountDownLatch
- Exchanger和無GC的Java
- Java Fork / Join進行并行編程
- Java最佳實踐–隊列之戰和鏈接的ConcurrentHashMap
- 如何在不到1ms的延遲內完成100K TPS
- 使用迭代器時如何避免ConcurrentModificationException
- 改善Java應用程序性能的快速技巧
- 阻塞隊列示例以執行命令
- 限制URL連接的信號量示例
- 執行命令的同步隊列示例
- 更一般的等待/通知機制的CountDownLatch示例
翻譯自: https://www.javacodegeeks.com/2011/09/java-concurrency-tutorial-semaphores.html