1. ReadWriteLock接口
ReadWriteLock是一個java接口,它並沒有繼承Lock接口。提供了readLock()和writeLock(),分別返回一個讀鎖和寫鎖。
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
2. ReentrantReadWriteLock使用
ReentrantReadWriteLock類內部定義了靜態內部類ReadLock和WriteLock。同時持有 readLock和writerLock對象。
public class ReadWriterLockUsage {
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private Map<String, String> map = new HashMap<>();
public String get(String key) {
try {
reentrantReadWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " readLock");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return map.get(key);
} finally {
reentrantReadWriteLock.readLock().unlock();
System.out.println(Thread.currentThread().getName() + " readUnlock");
}
}
public void put(String key, String value) {
try {
reentrantReadWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " writeLock");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
} finally {
reentrantReadWriteLock.writeLock().unlock();
System.out.println(Thread.currentThread().getName() + " writeUnLock");
}
}
public static void main(String[] args) {
ReadWriterLockUsage usage = new ReadWriterLockUsage();
for (int i = 0; i < 2; i++)
new Thread() {
@Override
public void run() {
super.run();
while (true)
usage.put("", "");
}
}.start();
for (int i = 0; i < 2; i++)
new Thread() {
@Override
public void run() {
super.run();
while (true)
usage.get("");
}
}.start();
}
}
運行結果可能如下
Thread-0 writeLock
Thread-0 writeUnLock
Thread-0 writeLock
Thread-0 writeUnLock
Thread-1 writeLock
Thread-1 writeUnLock
Thread-2 readLock
Thread-3 readLock
Thread-2 readUnlock
Thread-3 readUnlock
Thread-0 writeLock
Thread-0 writeUnLock
Thread-0 writeLock
Thread-0 writeUnLock
Thread-1 writeLock
Thread-1 writeUnLock
Thread-1 writeLock
Thread-1 writeUnLock
通過觀察結果我們可以發現writeLock同一時刻只能被一個線程獲得。而writeLock同一時刻可以被多個線程同時獲得。
3. 源碼解析
1. 鎖狀態
通過ReentrantLock的源碼解析我們瞭解到,鎖的狀態由 private volatile int state來控制。每當線程獲取到了鎖 state會加1,每當線程釋放了鎖state會減1。當state=0表示鎖當前處於非上鎖狀態。state定義在AbstractQueuedSynchronizer中。ReentrantLock持有一個AbstractQueuedSynchronizer對象。
那ReentrantReadWriteLock內部的鎖是怎麼實現的呢?我們知道ReentrantReadWriteLock內部有一個ReadLock和WriteLock。那麼他們之間是完全獨立的鎖嗎?如果不是獨立的鎖,那麼state怎麼來標識讀寫鎖加鎖的狀態和次數呢?
事實上ReadLock和WriteLock是共用同一個AQS對象。AQS的state的值可以標識鎖的狀態。在讀寫鎖的AQS實現中,int類型的state,我們知道int類型在計算機中有32位。它在讀寫所中,高16位標識讀鎖狀態。低16位表示寫鎖狀態。
static final int SHARED_SHIFT = 16;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;//值爲FFFF
//當前讀鎖(共享鎖)被獲取的數量 c >>> SHARED_SHIFT表示向右移動16個單位,結果是int的高16位
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//寫鎖(獨佔鎖)被獲取的數量,低16位
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
2.WriteLock源碼分析
首先我們來分析寫鎖的源碼,因爲寫鎖相對讀鎖更簡單一些,寫鎖和ReentrantLock都是獨佔鎖,所以我們先來分析它。還是從lock()開始分析
public static class WriteLock implements Lock, java.io.Serializable {
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquire(1);
}
}
我們知道sync的acquire(1)是個模板方法,先調用tryAcquire(1)方法如果獲取成功,直接返回,如果獲取失敗,把線程封裝成節點,加入隊列,並且通過自旋來獲取鎖。在ReentrantLock源碼分析中有詳細講解,讀者如果不熟悉可以,先看ReentrantLock源碼分析文章。我們重點來看下tryAcquire(1)在ReentrantReadWriteLock中的實現
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
*(如果讀鎖被獲取 或者 寫鎖被獲取而且獲取到的線程不是當前線程,獲取寫鎖失敗)
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
*(如果鎖的數量超過限制,獲取失敗)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*(否則,如果該線程是可重入獲取或隊列策略允許,則該線程有資格進行鎖定。 如果是這樣,更新狀態並設置所有者。)
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//如果 c != 0 and w == 0 表示讀鎖被線程佔有了,獲取鎖失敗
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);//獲取成功了 把當前線程設置爲鎖的擁有者線程
return true;
}
如果當前讀鎖被獲取了,再去獲取寫鎖會失敗。簡單吧,如果失敗了,走的還是ReentrantLock那一套
3. ReadLock源碼解析
我們知道讀鎖是共享鎖,同一時刻可以被多個線程同時獲取。接下來我們來分析下。還是從lock()方法開始
public static class ReadLock implements Lock, java.io.Serializable {
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
/**
* Acquires the read lock.
*
* <p>Acquires the read lock if the write lock is not held by
* another thread and returns immediately.
*
* <p>If the write lock is held by another thread then
* the current thread becomes disabled for thread scheduling
* purposes and lies dormant until the read lock has been acquired.
*/
public void lock() {
//如果寫鎖沒有被其他線程佔有立馬獲取到讀鎖
//如果寫鎖被其他線程佔有,那麼當前線程會阻塞直到讀鎖被獲取到
sync.acquireShared(1);
}
}
我們來看下sync.acquireShared(1)方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)//如果嘗試獲取共享鎖失敗
doAcquireShared(arg);
}
我們先來看下doAcquireShared()吧,如果是獨佔鎖嘗試獲取失敗,之後的流程我們是比較清楚的,我們可以對比觀察他們之間的區別
private void doAcquireShared(int arg) {
//加入到AQS隊列中 和獨佔鎖是一樣的
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {//通過無限循環實現自旋功能和獨佔鎖也是一樣的
final Node p = node.predecessor();
if (p == head) {//如果是隊首節點
int r = tryAcquireShared(arg);
if (r >= 0) {//如果獲取成功
//獲取成功共享鎖和獨佔鎖是有區別的,獨佔鎖獲取成功直接return。
//共享鎖如果獲取成功它會告訴下一個等待獲取共享鎖線程去獲取共享鎖
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
我們再來看下tryAcquireShared()的實現吧
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
關於鎖降級
鎖降級是指在獲取寫鎖的同時,讀鎖也能獲取到,當然這種情況只限於同一個線程。降級是對寫鎖而言,對於讀鎖個人覺得應該是鎖升級。附上java源碼的例子
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
最下面那個讀鎖其實被升級爲獨佔鎖了,同一時刻只能被一個線程獲取