但是有時我們需要對同步進行更多控制。 我們要么需要分別控制訪問類型(讀取和寫入),要么使用起來很麻煩,因為要么沒有明顯的互斥鎖,要么我們需要維護多個互斥鎖。
值得慶幸的是,Java 1.5中添加了鎖實用程序類,使這些問題更易于解決。
Java重入鎖
Java在java.util.concurrent.locks包中有一些鎖實現。
鎖的一般類很好地布置為接口:
- 鎖 –最簡單的鎖,可以獲取和釋放
- ReadWriteLock –具有讀和寫鎖類型的鎖實現–一次可以持有多個讀鎖,除非持有排他寫鎖
Java提供了我們關心的這些鎖的兩種實現–兩者都是可重入的(這僅意味著線程可以多次重新獲取同一鎖而沒有任何問題)。
- ReentrantLock –如您所料,可重入鎖實現
- ReentrantReadWriteLock –可重入ReadWriteLock實現
現在,讓我們看一些例子。
讀/寫鎖示例
那么如何使用鎖呢? 這很簡單:只需獲取并發布(永遠不要忘記發布-終于是您的朋友!)。
假設我們有一個非常簡單的情況,我們需要同步訪問一對變量。 一個是簡單的值,另一個是根據一些冗長的計算得出的。 首先,這就是我們如何使用synced關鍵字執行此操作。
public class Calculator {private int calculatedValue;private int value;public synchronized void calculate(int value) {this.value = value;this.calculatedValue = doMySlowCalculation(value);}public synchronized int getCalculatedValue() {return calculatedValue;}public synchronized int getValue() {return value;}
}
很簡單,但是如果我們有很多爭用或者執行大量讀取而寫入很少,則同步可能會影響性能。 由于頻繁讀取比寫入頻繁得多,因此使用ReadWriteLock可幫助我們最大程度地減少問題:
public class Calculator {private int calculatedValue;private int value;private ReadWriteLock lock = new ReentrantReadWriteLock();public void calculate(int value) {lock.writeLock().lock();try {this.value = value;this.calculatedValue = doMySlowCalculation(value);} finally {lock.writeLock().unlock();}}public int getCalculatedValue() {lock.readLock().lock();try {return calculatedValue;} finally {lock.readLock().unlock();}}public int getValue() {lock.readLock().lock();try {return value;} finally {lock.readLock().unlock();}}
}
該示例實際上顯示了使用同步的has的一個大優點:與使用顯式鎖相比,此方法簡潔明了且更加安全。 但是鎖提供了使用靈活性,而這是我們以前所沒有的。
在上面的示例中,我們可以讓數百個線程一次讀取相同的值而不會出現問題,并且只有在獲得寫入鎖定時才阻塞讀取器。 請記住:許多讀取器可以同時獲取讀取鎖定,但是在獲取寫入鎖定時不允許讀取器或寫入器。
更典型的用途
我們的第一個示例可能會讓您感到困惑或不完全相信顯式鎖是有用的。 難道他們還沒有其他用途嗎? 當然!
我們在Carfey使用顯式鎖來解決許多問題。 一個示例是您有可以同時運行的各種任務,但是您不希望同時運行多個相同類型的任務。 一種實現它的干凈方法是使用鎖。 可以通過同步來完成,但是鎖使我們能夠在超時后失敗。
值得一提的是,您會注意到我們使用了同步鎖和顯式鎖的組合-有時一個比另一個更干凈,更簡單。
public class TaskRunner {private Map<Class<? extends Runnable>, Lock> mLocks =new HashMap<Class<? extends Runnable>, Lock>();public void runTaskUniquely(Runnable r, int secondsToWait) {Lock lock = getLock(r.getClass());boolean acquired = lock.tryLock(secondsToWait, TimeUnit.SECONDS);if (acquired) {try {r.run();} finally {lock.unlock();}} else {// failure code here}}private synchronized Lock getLock(Class clazz) {Lock l = mLocks.get(clazz);if (l == null) {l = new ReentrantLock();mLocks.put(clazz, l);}return l;}
}
這兩個示例應該使您對如何同時使用計劃鎖和ReadWriteLocks有所了解。 與同步一樣,不必擔心重新獲得相同的鎖-JDK中提供的鎖是可重入的,因此不會有任何問題。
每當您處理并發時,都有危險。 永遠記住以下幾點:
- 釋放finally塊中的所有鎖。 這是規則1,有一個原因。
- 當心線程饑餓! 如果您有不想永久等待的許多讀者和偶爾的作家,那么ReentrantLocks中的公平設置可能會很有用。 如果其他線程不斷持有讀取鎖,那么編寫者可能會等待很長時間(可能永遠)。
- 盡可能使用同步。 您將避免錯誤并保持代碼清潔。
- 如果您不希望線程無限期等待獲取鎖,請使用tryLock() -這類似于數據庫具有的等待鎖超時。
就是這樣! 如果您有任何問題或意見,請隨時將其留在下面。
參考: Java并發第2部分–來自我們的JCG合作伙伴的Carent博客上的 Reentrant Locks 。
- Java并發教程–信號量
- Java并發教程–線程池
- Java并發教程–可調用,將來
- Java并發教程–阻塞隊列
- Java并發教程– CountDownLatch
- Exchanger和無GC的Java
- Java Fork / Join進行并行編程
- Java最佳實踐–隊列之戰和鏈接的ConcurrentHashMap
- 使用迭代器時如何避免ConcurrentModificationException
- 改善Java應用程序性能的快速技巧
翻譯自: https://www.javacodegeeks.com/2011/09/java-concurrency-tutorial-reentrant.html