Lock 是至關於 synchronized 更面向對象的同步方式,ReentrantLock 是 Lock 的實現。java
本文要介紹的 ReentrantReadWriteLock 跟 ReentrantLock 並無直接的關係,由於它們之間沒有繼承和實現的關係。git
可是 ReentrantReadWriteLock 擁有讀鎖(ReadLock)和寫鎖(WriteLock),它們分別都實現了 Lock。github
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
複製代碼
ReentrantReadWriteLock 在使用讀鎖時,其餘線程能夠進行讀操做,但不可進行寫操做。ReentrantReadWriteLock 在使用寫鎖時,其餘線程讀、寫操做都不能夠。ReentrantReadWriteLock 可以兼顧數據操做的原子性和讀寫的性能。算法
從 ReentrantReadWriteLock 的構造函數中能夠看出,它默認使用了非公平鎖。緩存
/** * Creates a new {@code ReentrantReadWriteLock} with * default (nonfair) ordering properties. */
public ReentrantReadWriteLock() {
this(false);
}
/** * Creates a new {@code ReentrantReadWriteLock} with * the given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
複製代碼
在 Java 中所謂公平鎖是指,每一個線程在獲取鎖時,會先查看此鎖維護的等待隊列,若是爲隊列空或者當前線程線程是等待隊列的第一個,則佔有鎖。不然就會加入到等待隊列中,之後按照 FIFO 的順序從隊列中取出。多線程
非公平鎖在獲取鎖時,不會遵循 FIFO 的順序,而是直接嘗試獲取鎖。若是獲取不到鎖,則像公平鎖同樣自動加入到隊列的隊尾等待。併發
非公平鎖的性能要高於公平鎖。框架
讀鎖是一個共享鎖。讀鎖是 ReentrantReadWriteLock 的內部靜態類,它的 lock()、trylock()、unlock() 都是委託 Sync 類實現。函數
Sync 是真正實現讀寫鎖功能的類,它繼承自 AbstractQueuedSynchronizer 。post
寫鎖是一個排他鎖。寫鎖也是 ReentrantReadWriteLock 的內部靜態類,它的 lock()、trylock()、unlock() 也都是委託 Sync 類實現。寫鎖的代碼相似於讀鎖,可是在同一時刻寫鎖是不能被多個線程所獲取,它是獨佔式鎖。
寫鎖能夠降級成讀鎖,下面會介紹鎖降級。
鎖降級是指先獲取寫鎖,再獲取讀鎖,而後再釋放寫鎖的過程 。鎖降級是爲了保證數據的可見性。鎖降級是 ReentrantReadWriteLock 重要特性之一。
值得注意的是,ReentrantReadWriteLock 並不能實現鎖升級。
RxCache 是一款支持 Java 和 Android 的 Local Cache 。目前,支持堆內存、堆外內存(off-heap memory)、磁盤緩存。
github地址:github.com/fengzhizi71…
RxCache 的 CacheRepository 類實現了緩存操做的類,它使用了 ReentrantReadWriteLock 用於保證緩存在讀寫時避免出現多線程的併發問題。
首先,建立一個讀寫鎖,並得到讀鎖、寫鎖的實例。
class CacheRepository {
private Memory memory;
private Persistence persistence;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
......
}
複製代碼
在緩存的讀操做時,使用讀鎖。
boolean containsKey(String key) {
readLock.lock();
try {
if (Preconditions.isBlank(key)) return false;
return (memory != null && memory.containsKey(key)) || (persistence != null && persistence.containsKey(key));
} finally {
readLock.unlock();
}
}
複製代碼
在緩存的寫操做時,使用寫鎖。
void remove(String key) {
writeLock.lock();
try {
if (Preconditions.isNotBlank(key)) {
if (memory != null) {
memory.evict(key);
}
if (persistence != null) {
persistence.evict(key);
}
}
} finally {
writeLock.unlock();
}
}
複製代碼
對於某一個方法,若是在讀操做作完以後要進行寫操做,則須要先釋放讀鎖,再獲取寫鎖(不然會死鎖)。寫操做以後,還須要進行讀操做的話,可使用鎖降級。
<T> Record<T> get(String key, Type type, CacheStrategy cacheStrategy) {
readLock.lock();
try {
Record<T> record = null;
if (Preconditions.isNotBlanks(key, type)) {
switch (cacheStrategy) {
case MEMORY: {
if (memory!=null) {
record = memory.getIfPresent(key);
}
break;
}
case PERSISTENCE: {
if (persistence!=null) {
record = persistence.retrieve(key, type);
}
break;
}
case ALL: {
if (memory != null) {
record = memory.getIfPresent(key);
}
if (record == null && persistence != null) {
record = persistence.retrieve(key, type);
if (memory!=null && record!=null && !record.isExpired()) { // 若是 memory 不爲空,record 不爲空,而且沒有過時
readLock.unlock(); // 先釋放讀鎖
writeLock.lock(); // 再獲取寫鎖
try {
if (record.isNeverExpire()) { // record永不過時的話,直接保存不須要計算ttl
memory.put(record.getKey(),record.getData());
} else {
long ttl = record.getExpireTime()- (System.currentTimeMillis() - record.getCreateTime());
memory.put(record.getKey(),record.getData(), ttl);
}
readLock.lock(); // 寫鎖在沒有釋放以前,得到讀鎖 (鎖降級)
} finally {
writeLock.unlock(); // 釋放寫鎖
}
}
}
break;
}
}
}
return record;
} finally {
readLock.unlock();
}
}
複製代碼
ReentrantReadWriteLock 讀寫鎖適用於讀多寫少的場景,以提升系統的併發性。所以,RxCache 使用讀寫鎖來實現緩存的操做。
RxCache 系列的相關文章:
Java與Android技術棧:每週更新推送原創技術文章,歡迎掃描下方的公衆號二維碼並關注,期待與您的共同成長和進步。