從這一節(jié)開(kāi)始介紹鎖里面的最后一個(gè)工具:讀寫(xiě)鎖(ReadWriteLock)。
ReentrantLock 實(shí)現(xiàn)了標(biāo)準(zhǔn)的互斥操作,也就是一次只能有一個(gè)線(xiàn)程持有鎖,也即所謂獨(dú)占鎖的概念。前面的章節(jié)中一直在強(qiáng)調(diào)這個(gè)特點(diǎn)。顯然這個(gè)特點(diǎn)在一定程度上面減低了吞吐量,實(shí)際上獨(dú)占鎖是一種保守的鎖策略,在這種情況下任何“讀/讀”,“寫(xiě)/讀”,“寫(xiě)/寫(xiě)”操作都不能同時(shí)發(fā)生。但是同樣需要強(qiáng)調(diào)的一個(gè)概念是,鎖是有一定的開(kāi)銷(xiāo)的,當(dāng)并發(fā)比較大的時(shí)候,鎖的開(kāi)銷(xiāo)就比較客觀(guān)了。所以如果可能的話(huà)就盡量少用鎖,非要用鎖的話(huà)就嘗試看能否改造為讀寫(xiě)鎖。
ReadWriteLock描述的是:一個(gè)資源能夠被多個(gè)讀線(xiàn)程訪(fǎng)問(wèn),或者被一個(gè)寫(xiě)線(xiàn)程訪(fǎng)問(wèn),但是不能同時(shí)存在讀寫(xiě)線(xiàn)程。也就是說(shuō)讀寫(xiě)鎖使用的場(chǎng)合是一個(gè)共享資源被大量讀取操作,而只有少量的寫(xiě)操作(修改數(shù)據(jù))。清單1描述了ReadWriteLock的API。
清單1 ReadWriteLock 接口
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
清單1描述的ReadWriteLock結(jié)構(gòu),這里需要說(shuō)明的是ReadWriteLock并不是Lock的子接口,只不過(guò)ReadWriteLock借助Lock來(lái)實(shí)現(xiàn)讀寫(xiě)兩個(gè)視角。在ReadWriteLock中每次讀取共享數(shù)據(jù)就需要讀取鎖,當(dāng)需要修改共享數(shù)據(jù)時(shí)就需要寫(xiě)入鎖。看起來(lái)好像是兩個(gè)鎖,但其實(shí)不盡然,在下一節(jié)中的分析中會(huì)解釋這點(diǎn)奧秘。
在JDK 6里面ReadWriteLock的實(shí)現(xiàn)是ReentrantReadWriteLock。
清單2 SimpleConcurrentMap
package xylz.study.concurrency.lock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class SimpleConcurrentMap<K, V> implements Map<K, V> {
final ReadWriteLock lock = new ReentrantReadWriteLock();
final Lock r = lock.readLock();
final Lock w = lock.writeLock();
final Map<K, V> map;
public SimpleConcurrentMap(Map<K, V> map) {
this.map = map;
if (map == null) throw new NullPointerException();
}
public void clear() {
w.lock();
try {
map.clear();
} finally {
w.unlock();
}
}
public boolean containsKey(Object key) {
r.lock();
try {
return map.containsKey(key);
} finally {
r.unlock();
}
}
public boolean containsValue(Object value) {
r.lock();
try {
return map.containsValue(value);
} finally {
r.unlock();
}
}
public Set<java.util.Map.Entry<K, V>> entrySet() {
throw new UnsupportedOperationException();
}
public V get(Object key) {
r.lock();
try {
return map.get(key);
} finally {
r.unlock();
}
}
public boolean isEmpty() {
r.lock();
try {
return map.isEmpty();
} finally {
r.unlock();
}
}
public Set<K> keySet() {
r.lock();
try {
return new HashSet<K>(map.keySet());
} finally {
r.unlock();
}
}
public V put(K key, V value) {
w.lock();
try {
return map.put(key, value);
} finally {
w.unlock();
}
}
public void putAll(Map<? extends K, ? extends V> m) {
w.lock();
try {
map.putAll(m);
} finally {
w.unlock();
}
}
public V remove(Object key) {
w.lock();
try {
return map.remove(key);
} finally {
w.unlock();
}
}
public int size() {
r.lock();
try {
return map.size();
} finally {
r.unlock();
}
}
public Collection<V> values() {
r.lock();
try {
return new ArrayList<V>(map.values());
} finally {
r.unlock();
}
}
}
清單2描述的是用讀寫(xiě)鎖實(shí)現(xiàn)的一個(gè)線(xiàn)程安全的Map。其中需要特別說(shuō)明的是并沒(méi)有實(shí)現(xiàn)entrySet()方法,這是因?yàn)閷?shí)現(xiàn)這個(gè)方法比較復(fù)雜,在后面章節(jié)中講到ConcurrentHashMap的時(shí)候會(huì)具體談這些細(xì)節(jié)。另外這里keySet()和values()也沒(méi)有直接返回Map的視圖,而是一個(gè)映射原有元素的新視圖,其實(shí)這個(gè)entrySet()一樣,是為了保護(hù)原始Map的數(shù)據(jù)邏輯,防止不正確的修改導(dǎo)致原始Map發(fā)生數(shù)據(jù)錯(cuò)誤。特別說(shuō)明的是在沒(méi)有特別需求的情況下沒(méi)有必要按照清單2寫(xiě)一個(gè)線(xiàn)程安全的Map實(shí)現(xiàn),因?yàn)镃oncurrentHashMap已經(jīng)完成了此操作。
ReadWriteLock需要嚴(yán)格區(qū)分讀寫(xiě)操作,如果讀操作使用了寫(xiě)入鎖,那么降低讀操作的吞吐量,如果寫(xiě)操作使用了讀取鎖,那么就可能發(fā)生數(shù)據(jù)錯(cuò)誤。
另外ReentrantReadWriteLock還有以下幾個(gè)特性:
- 公平性
- 非公平鎖(默認(rèn)) 這個(gè)和獨(dú)占鎖的非公平性一樣,由于讀線(xiàn)程之間沒(méi)有鎖競(jìng)爭(zhēng),所以讀操作沒(méi)有公平性和非公平性,寫(xiě)操作時(shí),由于寫(xiě)操作可能立即獲取到鎖,所以會(huì)推遲一個(gè)或多個(gè)讀操作或者寫(xiě)操作。因此非公平鎖的吞吐量要高于公平鎖。
- 公平鎖 利用AQS的CLH隊(duì)列,釋放當(dāng)前保持的鎖(讀鎖或者寫(xiě)鎖)時(shí),優(yōu)先為等待時(shí)間最長(zhǎng)的那個(gè)寫(xiě)線(xiàn)程分配寫(xiě)入鎖,當(dāng)前前提是寫(xiě)線(xiàn)程的等待時(shí)間要比所有讀線(xiàn)程的等待時(shí)間要長(zhǎng)。同樣一個(gè)線(xiàn)程持有寫(xiě)入鎖或者有一個(gè)寫(xiě)線(xiàn)程已經(jīng)在等待了,那么試圖獲取公平鎖的(非重入)所有線(xiàn)程(包括讀寫(xiě)線(xiàn)程)都將被阻塞,直到最先的寫(xiě)線(xiàn)程釋放鎖。如果讀線(xiàn)程的等待時(shí)間比寫(xiě)線(xiàn)程的等待時(shí)間還有長(zhǎng),那么一旦上一個(gè)寫(xiě)線(xiàn)程釋放鎖,這一組讀線(xiàn)程將獲取鎖。
- 重入性
- 讀寫(xiě)鎖允許讀線(xiàn)程和寫(xiě)線(xiàn)程按照請(qǐng)求鎖的順序重新獲取讀取鎖或者寫(xiě)入鎖。當(dāng)然了只有寫(xiě)線(xiàn)程釋放了鎖,讀線(xiàn)程才能獲取重入鎖。
- 寫(xiě)線(xiàn)程獲取寫(xiě)入鎖后可以再次獲取讀取鎖,但是讀線(xiàn)程獲取讀取鎖后卻不能獲取寫(xiě)入鎖。
- 另外讀寫(xiě)鎖最多支持65535個(gè)遞歸寫(xiě)入鎖和65535個(gè)遞歸讀取鎖。
- 鎖降級(jí)
- 寫(xiě)線(xiàn)程獲取寫(xiě)入鎖后可以獲取讀取鎖,然后釋放寫(xiě)入鎖,這樣就從寫(xiě)入鎖變成了讀取鎖,從而實(shí)現(xiàn)鎖降級(jí)的特性。
- 鎖升級(jí)
- 讀取鎖是不能直接升級(jí)為寫(xiě)入鎖的。因?yàn)楂@取一個(gè)寫(xiě)入鎖需要釋放所有讀取鎖,所以如果有兩個(gè)讀取鎖視圖獲取寫(xiě)入鎖而都不釋放讀取鎖時(shí)就會(huì)發(fā)生死鎖。
- 鎖獲取中斷
- 讀取鎖和寫(xiě)入鎖都支持獲取鎖期間被中斷。這個(gè)和獨(dú)占鎖一致。
- 條件變量
- 寫(xiě)入鎖提供了條件變量(Condition)的支持,這個(gè)和獨(dú)占鎖一致,但是讀取鎖卻不允許獲取條件變量,將得到一個(gè)
UnsupportedOperationException
異常。
- 重入數(shù)
- 讀取鎖和寫(xiě)入鎖的數(shù)量最大分別只能是65535(包括重入數(shù))。這在下節(jié)中有介紹。
上面幾個(gè)特性對(duì)讀寫(xiě)鎖的理解很有幫助,而且也是必要的,另外在下一節(jié)中講ReadWriteLock的實(shí)現(xiàn)會(huì)用到這些知識(shí)的。
©2009-2014 IMXYLZ
|求賢若渴