思維導圖:
引言:
本文的主要內容是介紹兩種顯式鎖的使用。一種是Lock,一種是ReadWriteLock。所以,本文可以歸類爲使用部分:
- 使用部分:介紹Lock,其實現類是ReentrantLock,功能則是補充synchronized的不足。也會介紹ReadWriteLock,用於讀多寫少的併發程序。
一.Lock
顯式鎖Lock可以作爲synchronized在功能不足時的補充,比如定時獲取鎖這些操作。Lock並不能替代synchronized,這個小節會介紹顯式鎖Lock的一些常見用法。
1.1 加鎖方式
如何使用Lock進行各種各樣的加鎖呢?
1.1.1 直接加鎖
public void addLock(int n) {
Lock lock = new ReentrantLock();
lock.lock();
try{
//別加鎖的操作
}finally {
//釋放鎖
lock.unlock();
}
}
1.1.2 輪詢鎖
我們可以使用Lock進行輪詢操作以獲取鎖,這樣做可以避免發生順序死鎖。如下代碼所示:
public class DeadlockAvoidance {
private static Random rnd = new Random();
public boolean transferMoney(Account fromAcct, Account toAcct, DollarAmount amount, long timeout, TimeUnit unit) throws InsufficientFundsException, InterruptedException {
long fixedDelay = getFixedDelayComponentNanos(timeout, unit);
long randMod = getRandomDelayModulusNanos(timeout, unit);
long stopTime = System.nanoTime() + unit.toNanos(timeout);
while (true) {
if (fromAcct.lock.tryLock()) {
try {
if (toAcct.lock.tryLock()) {
try {
if (fromAcct.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException();
} else {
fromAcct.debit(amount);
toAcct.credit(amount);
return true;
}
} finally {
toAcct.lock.unlock();
}
}
} finally {
fromAcct.lock.unlock();
}
}
if (System.nanoTime() < stopTime) {
return false;
}
NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
}
}
private static final int DELAY_FIXED = 1;
private static final int DELAY_RANDOM = 2;
static long getFixedDelayComponentNanos(long timeout, TimeUnit unit) {
return DELAY_FIXED;
}
static long getRandomDelayModulusNanos(long timeout, TimeUnit unit) {
return DELAY_RANDOM;
}
static class DollarAmount implements Comparable<DollarAmount> {
public int compareTo(DollarAmount other) {
return 0;
}
DollarAmount(int dollars) {
}
}
class Account {
public Lock lock;
void debit(DollarAmount d) {
}
void credit(DollarAmount d) {
}
DollarAmount getBalance() {
return null;
}
}
class InsufficientFundsException extends Exception {
}
}
1.1.3 定時鎖
可以利用Lock實現嘗試在規定時間內獲取鎖,超時則放棄的功能。如下所示:
public class TimedLocking {
private Lock lock = new ReentrantLock();
public boolean trySendOnSharedLine(String message, long timeout, TimeUnit unit) throws InterruptedException {
long nanosToLock = unit.toNanos(timeout) - estimatedNanosToSend(message);
if (!lock.tryLock(nanosToLock, NANOSECONDS)) {
return false;
}
try {
return sendOnSharedLine(message);
} finally {
lock.unlock();
}
}
private boolean sendOnSharedLine(String message) {
/* send something */
return true;
}
long estimatedNanosToSend(String message) {
return message.length();
}
}
1.1.4 可中斷鎖
Lock也有對中斷操作保持響應的可中斷鎖的功能,如下所示:
public class InterruptibleLocking {
private Lock lock = new ReentrantLock();
public boolean sendOnSharedLine(String message) throws InterruptedException {
lock.lockInterruptibly();
try {
return cancellableSendOnSharedLine(message);
} finally {
lock.unlock();
}
}
private boolean cancellableSendOnSharedLine(String message) throws InterruptedException {
/* send something */
return true;
}
}
1.2 使用要點
使用顯示鎖時有以下幾個需要注意的地方。
- 在併發程序發生激烈的競爭時,使用顯示鎖的速度並不會比使用synchronized更快
- ReetrantLock可以使用公平鎖或者非公平鎖。公平鎖時依照請求順序獲得鎖,而非公平鎖的可以插隊,如果該鎖的狀態在發出請求後恰好可用的話
- 使用ReetrantLock在內存的語義上和使用synchronized相同,他們提供的性能也相差不遠,所以,一般情況下還是使用synchronized比較方面,出錯的可能性也較小。但是,如果需要一些特殊的功能,比如公平性,可中斷,可等待時,就需要使用ReetrantLock了,總的來說,ReetrantLock是synchronized在功能上的補充。
二.ReadWriteLock
對於某些併發程序來說,其讀操作的數量遠遠大於寫操作的數量。在讀取操作時,我們其實只需要保證內存的可見性就好,並不需要獲取整個鎖。只有當需要進行寫操作是,才需要獲取獨佔鎖。此時,我們就可以使用ReadWriteLock了。如下所示:
public class ReadWriteMap <K,V> {
private final Map<K, V> map;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock r = lock.readLock();
private final Lock w = lock.writeLock();
public ReadWriteMap(Map<K, V> map) {
this.map = map;
}
public V put(K key, V value) {
w.lock();
try {
return map.put(key, value);
} finally {
w.unlock();
}
}
public V get(Object key) {
r.lock();
try {
return map.get(key);
} finally {
r.unlock();
}
}
}