目錄
1 讀寫鎖規則
讀-讀可以共存,讀-寫不能共存,寫-寫不能共存2 讀寫鎖使用
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 讀方法
private void read() {
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread() + "read start");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "read end");
readWriteLock.readLock().unlock();
}
// 寫方法
private void write() {
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread() + "write start");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "write end");
readWriteLock.writeLock().unlock();
}
測試代碼
@Test
public void readWriteLockTest() throws InterruptedException {
for (int i = 0; i < 5; i ++) {
new Thread(() -> read()).start();
}
for (int i = 0; i < 5; i ++) {
new Thread(() -> write()).start();
}
TimeUnit.MINUTES.sleep(1);
}
輸出
Thread[Thread-0,5,main]read start
Thread[Thread-1,5,main]read start
Thread[Thread-2,5,main]read start
Thread[Thread-3,5,main]read start
Thread[Thread-3,5,main]read end
Thread[Thread-0,5,main]read end
Thread[Thread-1,5,main]read end
Thread[Thread-2,5,main]read end
Thread[Thread-5,5,main]write start
Thread[Thread-5,5,main]write end
Thread[Thread-6,5,main]write start
Thread[Thread-6,5,main]write end
Thread[Thread-4,5,main]read start
Thread[Thread-4,5,main]read end
Thread[Thread-7,5,main]write start
Thread[Thread-7,5,main]write end
Thread[Thread-8,5,main]write start
Thread[Thread-8,5,main]write end
Thread[Thread-9,5,main]write start
Thread[Thread-9,5,main]write end
當然每次執行結果可能不一樣,但是總體可以得出結論,讀讀可以併發,讀寫不能併發,寫寫不呢併發3 讀寫鎖原理
2 認識ReadWriteLock接口
java中讀寫鎖是實現了ReadWriteLock接口,這個接口比較簡單,只有兩個方法
public interface ReadWriteLock {
/**
* @return 獲取讀鎖
*/
Lock readLock();
/**
* @return 獲取寫鎖
*/
Lock writeLock();
}
ReentrantReadWriteLock方法的實現
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
可以看到返回的是ReentrantReadWriteLock的內部類
3 寫鎖Sync類中基本變量
/**
* 讀寫鎖共用同步狀態變量,來同時維護讀和寫的狀態,按位切割使用,高16位表示讀的狀態,低16位表示寫的狀態
*/
//讀鎖狀態偏移量,左移16位16位
static final int SHARED_SHIFT = 16;
//讀鎖操作的基本單元,讀鎖狀態+1,則狀態變量值+SHARED_UNIT
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
//可重入狀態的最大值
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
//寫鎖掩碼,將同步狀態變量和掩碼位與就可以得出寫鎖的狀態
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
4 寫鎖加鎖方法
public void lock() {
sync.acquire(1);
}
可以看到用到的還是AQS中的模版方法,acquireShared方法執行過程在筆者JUC併發工具二-AQS中講過,感興趣的同學可以先了解一下
然後看acquireShared方法中的第一步tryAcquireShared在讀鎖中的實現
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
// 獲取當前state資源的值
int c = getState();
// 獲取當前寫鎖狀態
int w = exclusiveCount(c);
if (c != 0) {
// 如果c!=0並且w=0表示持有共享鎖,獲取資源失敗
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
// 寫鎖重入次數已超過最大值
throw new Error("Maximum lock count exceeded");
// 獲取資源成功,修改state變量
setState(c + acquires);
return true;
}
// 對於非公平鎖,writerShouldBlock總是false;對於公平鎖就是在重入鎖章節中講到的hasQueuedPredecessors方法
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 設置當前線程佔用寫鎖資源
setExclusiveOwnerThread(current);
return true;
}
5 寫鎖解鎖方法
public void unlock() {
sync.release(1);
}
AQS章節中已經講過release方法執行過程,這裏直接看tryRelease代碼,和ReentrantLock代碼差不多
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
// 如果不是佔用線程不是當前線程直接拋異常
throw new IllegalMonitorStateException();
// 查看剩餘需要解鎖次數
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
// 如果當前線程解鎖完畢釋放線程佔用
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
6 讀鎖加鎖方法
public void lock() {
sync.acquireShared(1);
}
可以看到用到的還是AQS中的模版方法,acquireShared方法執行過程在筆者JUC併發工具二-AQS中講過,感興趣的同學可以先了解一下
然後看acquireShared方法中的第一步tryAcquireShared在讀鎖中的實現
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
// 如果有寫鎖佔用並且佔用線程不是當前線程的話返回-1,獲取資源失敗
return -1;
int r = sharedCount(c);
// readerShouldBlock根據公平和非公平策略限制
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);
}
7 讀鎖釋放
public void unlock() {
sync.releaseShared(1);
}
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
// 把firstReader擲空
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
// 自旋更新state值
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}