1. 程式人生 > >java.util.concurrent.Locks使用指南

java.util.concurrent.Locks使用指南

包含 cap rdquo mov The dha 沒有 code 應用程序

1.概述

簡而言之,鎖是一種比標準同步更靈活,更復雜的線程同步機制

自Java 1.5以來Lock接口一直存在。它在java.util.concurrent.lock包中定義,它提供了大量的鎖定操作。

在本文中,我們將探討Lock接口及其應用程序的不同實現

2. 鎖定(Lock)同步塊(Synchronized )之間的差異

  • 同步被完全包含在方法中(創建和釋放鎖在同一個方法內); Lock可以將lock()unlock()放在不同的方法中
  • synchronized塊不支持公平性,一旦鎖釋放,任何線程可能獲得鎖;    通過指定公平屬性,我們可以在
    Lock API中實現公平它確保最長的等待線程可以訪問鎖
  • 如果線程無法訪問同步,則會阻塞該線程; Lock API提供的tryLock()方法,只有當鎖可用且沒有任何其他線程持有時,線程才會獲取鎖定。這減少了線程等待鎖定的阻塞時間(可以設置嘗試獲取鎖的時間)
  • 處於“等待”狀態以獲取對同步塊的訪問的線程不能被中斷; Lock API提供了一種方法lockInterruptibly() 其可以用來中斷等待鎖線程。

3. Lock API

我們來看看Lock接口中的方法

  • void lock() -獲取鎖(如果可用);
    如果鎖不可用,則線程會被阻塞,直到鎖被釋放
  • void lockInterruptibly() - 這類似於 lock(),但它允許被阻塞的線程被中斷並通過拋出的java.lang.InterruptedException恢復執行
  • boolean tryLock() - 這是 lock()方法的非阻塞版本; 它會立即嘗試獲取鎖定,如果鎖定成功則返回true
  • boolean tryLock(long timeout,TimeUnit timeUnit) -這類似於 tryLock(),除了它在放棄嘗試獲取 Lock之前等待給定的超時
  • void unlock() - 解鎖Lock實例

執行完畢應始終釋放鎖以避免死鎖情況。

使用鎖的推薦代碼塊應包含try / catchfinally塊:

Lock lock = ...; 
lock.lock();
try {
    // access to the shared resource
} finally {
    lock.unlock();
}

除了 Lock接口之外我們還有一個ReadWriteLock接口,它維護一對鎖,一個用於只讀操作,另一個用於寫操作。只要沒有寫入,讀鎖可以由多個線程同時保持。

ReadWriteLock聲明獲取讀或寫鎖的方法:

  • Lock readLock() -返回用於讀取的鎖
  • Lock writeLock() - 返回用於寫入的鎖

4. 鎖的實現

4.1.ReentrantLock(重入鎖)

ReentrantLock類實現Lock接口。它提供了相同的並發和內存語義.

public class SharedObject {
    //...
    ReentrantLock lock = new ReentrantLock();
    int counter = 0;
 
    public void perform() {
        lock.lock();
        try {
            // Critical section here
            count++;
        } finally {
            lock.unlock();
        }
    }
    //...
}

我們需要確保try-finally中包裝lock()和unlock()調用以避免死鎖情況。

讓我們看看tryLock()如何使用

public void performTryLock(){
    //...
    boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS);
     
    if(isLockAcquired) {
        try {
            //Critical section here
        } finally {
            lock.unlock();
        }
    }
    //...
}

在這種情況下,調用tryLock()的線程將最多等待一秒鐘(如果直接拿到鎖則不用等待),並且如果鎖不可用則放棄等待。

4.2.ReentrantReadWriteLock

ReentrantReadWriteLock類實現ReadWriteLock接口。

讓我們看一下線程獲取ReadLockWriteLock規則

  • 讀鎖 - 如果沒有線程獲得寫鎖或請求它,則多個線程可以獲取讀鎖
  • 寫鎖- 如果沒有線程正在讀取或寫入,則只有一個線程可以獲取寫入鎖

讓我們看看如何使用ReadWriteLock

public class SynchronizedHashMapWithReadWriteLock {
 
    Map<String,String> syncHashMap = new HashMap<>();
    ReadWriteLock lock = new ReentrantReadWriteLock();
    // ...
    Lock writeLock = lock.writeLock();
 
    public void put(String key, String value) {
        try {
            writeLock.lock();
            syncHashMap.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }
    ...
    public String remove(String key){
        try {
            writeLock.lock();
            return syncHashMap.remove(key);
        } finally {
            writeLock.unlock();
        }
    }
    //...
}

Lock readLock = lock.readLock();
//...
public String get(String key){
    try {
        readLock.lock();
        return syncHashMap.get(key);
    } finally {
        readLock.unlock();
    }
}
 
public boolean containsKey(String key) {
    try {
        readLock.lock();
        return syncHashMap.containsKey(key);
    } finally {
        readLock.unlock();
    }
}

4.3.StampedLock

StampedLock在Java 8中引入。它還支持讀寫鎖定。但是,鎖獲取方法會返回一個用於釋放鎖或檢查鎖是否仍然有效的戳記:

public class StampedLockDemo {
    Map<String,String> map = new HashMap<>();
    private StampedLock lock = new StampedLock();
 
    public void put(String key, String value){
        long stamp = lock.writeLock();
        try {
            map.put(key, value);
        } finally {
            lock.unlockWrite(stamp);
        }
    }
 
    public String get(String key) throws InterruptedException {
        long stamp = lock.readLock();
        try {
            return map.get(key);
        } finally {
            lock.unlockRead(stamp);
        }
    }
}

StampedLock提供的另一個功能是樂觀鎖定。大多數時候,讀操作不需要等待寫操作完成,因此不需要真正的讀鎖。

如果出現並發,我們可以升級到讀鎖:

public String readWithOptimisticLock(String key) {
    long stamp = lock.tryOptimisticRead();
    String value = map.get(key);
 
    if(!lock.validate(stamp)) {
        stamp = lock.readLock();
        try {
            return map.get(key);
        } finally {
            lock.unlock(stamp);               
        }
    }
    return value;
}

5.使用條件

條件類提供了一個線程等待在執行關鍵部分出現一些狀況的能力。

當線程獲得對臨界區的訪問但沒有執行其操作的必要條件時,可能會發生這種情況。例如,讀者線程可以訪問共享隊列的鎖,該隊列仍然沒有任何數據要消耗。

傳統上,Java 為線程互通提供了wait(),notify()和notifyAll()方法。條件有類似的機制,但另外,我們可以指定多個條件:

public class ReentrantLockWithCondition {
 
    Stack<String> stack = new Stack<>();
    int CAPACITY = 5;
 
    ReentrantLock lock = new ReentrantLock();
    Condition stackEmptyCondition = lock.newCondition();
    Condition stackFullCondition = lock.newCondition();
 
    public void pushToStack(String item){
        try {
            lock.lock();
            while(stack.size() == CAPACITY) {
                stackFullCondition.await();
            }
            stack.push(item);
            stackEmptyCondition.signalAll();
        } finally {
            lock.unlock();
        }
    }
 
    public String popFromStack() {
        try {
            lock.lock();
            while(stack.size() == 0) {
                stackEmptyCondition.await();
            }
            return stack.pop();
        } finally {
            stackFullCondition.signalAll();
            lock.unlock();
        }
    }
}

對比:

  Condition提供的方法和Java原生的wait(),notify(),notifyAll()功能類似,但是可以同時創建多個Condition條件,而java原生則不能指定特定的條件。

六,結論

在本文中,我們已經看到了Lock接口和新引入的StampedLock類的不同實現我們還探討了如何使用Condition類來處理多個條件。

java.util.concurrent.Locks使用指南