四、lock.writeLock().lock() (寫)剖析
public void ReentrantReadWriteLock.WriteLock#lock() {
sync.acquire(1);
}
public final void AbstractQueuedSynchronizer#acquire(int arg) {
if (!tryAcquire(arg) && // 返回true的話,就是拿到lock了,無需往下走了
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // tryAcquire返回了 false,走這裏進入隊列
selfInterrupt();
}
protected final boolean ReentrantReadWriteLock.Sync#tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState(); // 獲取狀態
int w = exclusiveCount(c); // 獲取 write 鎖數量
if (c != 0) {
// 如果 write 鎖數量不是 0 或者說目前不是重入
if (w == 0 || current != getExclusiveOwnerThread())
return false; // 獲取鎖失敗
// 既然走到了這裏,說明: w!= 0 && current == getExclusiveOwnerThread(),那麼就是write數量不是0,並且是重入操作。
if (w + exclusiveCount(acquires) > MAX_COUNT) //查看是否超限
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires); // 重入,狀態 +1
return true; //成功拿到鎖
}
/*
* 1. writer 是否應該阻塞。 NonfairSync一直不用阻塞。 FairSync的話,就看下隊列中有沒有排毒跌,有的話,就阻塞
* 2. CAS 設置 state
*/
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false; // 1.阻塞 或者:2.CAS設置status不成功。返回 false
setExclusiveOwnerThread(current);
return true;
}
=== 點擊查看top目錄 ===
五、場景分析
- 場景一:thread1 在 read,thread2-threadN 接着 read
- 場景二:thread1 在 read,thread1 準備來 write
- 場景三:thread1 在 read,thread2 準備來 write
- 場景四:thread1 在 read,thread1 重入 read (測試read重入)
- 場景五:thread1 在 write,thread2-N 準備來 read
- 場景六:thread1 在 write,thread2 準備來 write
- 場景七:thread1 在 write ,thread1 準備來 read (測試降級)
- 場景八:thread1 在 write, thread再次 write (測試write鎖重入)
場景一:thread1 在 read,thread2-threadN 接着 read
代碼:
public class _14_01_TestReadWriteLock {
public static void main(String[] args) throws Exception {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " wait to lock ...");
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " get the lock ... ===");
System.out.println("點擊任意鍵喚醒線程 ...");
Scanner sc = new Scanner(System.in);
sc.nextLine();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " ready to unlock ...");
lock.readLock().unlock();
}
}, "Read_" + i).start();
}
}
}
輸出:
Read_0 wait to lock ...
Read_1 wait to lock ...
Read_1 get the lock ... ===
點擊任意鍵喚醒線程 ...
Read_0 get the lock ... ===
點擊任意鍵喚醒線程 ...
Read_2 wait to lock ...
Read_2 get the lock ... ===
點擊任意鍵喚醒線程 ...
Read_3 wait to lock ...
Read_3 get the lock ... ===
點擊任意鍵喚醒線程 ...
Read_4 wait to lock ...
Read_4 get the lock ... ===
點擊任意鍵喚醒線程 ...
Read_3 ready to unlock ...
Read_2 ready to unlock ...
Read_1 ready to unlock ...
Read_0 ready to unlock ...
Read_4 ready to unlock ...
結論:
在第一個thread並未釋放鎖的時候,其他thread不阻塞,直接獲取鎖,往下跑。
=== 點擊查看top目錄 ===
場景二:thread1 在 read,thread1 準備來 write
看下代碼:
public class _14_02_TestReadWriteLock {
public static void main(String[] args) throws Exception{
new Thread(() -> {
ReadWriteLock lock = new ReentrantReadWriteLock();
System.out.println(Thread.currentThread().getName() + " wait to read lock ...");
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " get the read lock ... ===");
System.out.println(Thread.currentThread().getName() + " wait to lock write ...");
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " get the lock write ...");
System.out.println(Thread.currentThread().getName() + " ready to unlock read ...");
lock.readLock().unlock();
System.out.println(Thread.currentThread().getName() + " ready to unlock write ...");
lock.writeLock().unlock();
}).start();
}
}
輸出:
Thread-0 wait to read lock ...
Thread-0 get the read lock ... ===
Thread-0 wait to lock write ...
... 阻塞當中 ...
上面代碼,read鎖已經獲得,但是write鎖阻塞,read鎖無法釋放,上面死鎖了。
注意上方的 c = getState() = 65536 ,剛剛好是 2的 16次方,因爲read鎖是在 高16位,所以 read 鎖數量是1的話,那麼就是 65536,真正的數量右移 16位。
=== 點擊查看top目錄 ===
場景三:thread1 在 read,thread2 準備來 write
同理,堵塞,等待 thread1 釋放read 鎖,釋放後,可以獲得鎖
// 場景三:thread1 在 read,thread2 準備來 write
public class _14_03_TestReadWriteLock {
public static void main(String[] args) throws Exception{
ReadWriteLock lock = new ReentrantReadWriteLock();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " wait to read lock ...");
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " get the read lock ... ===");
System.out.println("點擊任意鍵喚醒線程 ...");
Scanner sc = new Scanner(System.in);
sc.nextLine();
System.out.println(Thread.currentThread().getName() + " ready to unlock read ...");
lock.readLock().unlock();
}).start();
Thread.sleep(2000);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " wait to lock write ...");
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " get the lock write ...");
System.out.println(Thread.currentThread().getName() + " ready to unlock write ...");
lock.writeLock().unlock();
}).start();
}
}
}
輸出:
Thread-0 wait to read lock ...
Thread-0 get the read lock ... ===
點擊任意鍵喚醒線程 ...
Thread-1 wait to lock write ...
Thread-2 wait to lock write ...
Thread-3 wait to lock write ...
Thread-4 wait to lock write ...
Thread-5 wait to lock write ...
Thread-0 ready to unlock read ...
Thread-1 get the lock write ...
Thread-1 ready to unlock write ...
Thread-2 get the lock write ...
Thread-2 ready to unlock write ...
Thread-3 get the lock write ...
Thread-3 ready to unlock write ...
Thread-4 get the lock write ...
Thread-4 ready to unlock write ...
Thread-5 get the lock write ...
Thread-5 ready to unlock write ...
=== 點擊查看top目錄 ===
場景四:thread1 在 read,thread1 重入 read (測試read重入)
代碼:
// 場景四:thread1 在 read,thread1 重入 read
public class _14_07_TestReadWriteLock {
public static void main(String[] args) throws Exception {
new Thread(() -> {
ReadWriteLock lock = new ReentrantReadWriteLock();
System.out.println(Thread.currentThread().getName() + " wait to read lock ...");
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " get the read lock ... ===");
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " get the read lock2 ... ===");
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " get the read lock3 ... ===");
System.out.println(Thread.currentThread().getName() + " ready to read unlock ...");
lock.readLock().unlock();
System.out.println(Thread.currentThread().getName() + " ready to read unlock2 ...");
lock.readLock().unlock();
System.out.println(Thread.currentThread().getName() + " ready to read unlock3 ...");
lock.readLock().unlock();
}).start();
}
}
輸出:
Thread-0 wait to read lock ...
Thread-0 get the read lock ... ===
Thread-0 get the read lock2 ... ===
Thread-0 get the read lock3 ... ===
Thread-0 ready to read unlock ...
Thread-0 ready to read unlock2 ...
Thread-0 ready to read unlock3 ...
場景五:thread1 在 write,thread2-N 準備來 read
代碼:
public class _14_05_TestReadWriteLock {
public static void main(String[] args) throws Exception{
ReadWriteLock lock = new ReentrantReadWriteLock();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " wait to write lock ...");
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " get the write lock ... ===");
System.out.println("點擊任意鍵喚醒線程 ...");
Scanner sc = new Scanner(System.in);
sc.nextLine();
System.out.println(Thread.currentThread().getName() + " ready to unlock write ...");
lock.writeLock().unlock();
}).start();
Thread.sleep(5000);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " wait to read lock ... ");
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " get the read lock ... ===");
System.out.println(Thread.currentThread().getName() + " ready to read unlock ...");
lock.readLock().unlock();
}).start();
}
}
}
輸出:
Thread-0 wait to write lock ...
Thread-0 get the write lock ... ===
點擊任意鍵喚醒線程 ...
Thread-1 wait to read lock ...
Thread-2 wait to read lock ...
Thread-3 wait to read lock ...
Thread-4 wait to read lock ...
Thread-5 wait to read lock ...
Thread-0 ready to unlock write ...
Thread-1 get the read lock ... ===
Thread-1 ready to read unlock ...
Thread-2 get the read lock ... ===
Thread-2 ready to read unlock ...
Thread-3 get the read lock ... ===
Thread-3 ready to read unlock ...
Thread-5 get the read lock ... ===
Thread-5 ready to read unlock ...
Thread-4 get the read lock ... ===
Thread-4 ready to read unlock ...
read共享鎖解析
=== 點擊查看top目錄 ===
場景六:thread1 在 write,thread2 準備來 write
代碼
public class _14_06_TestReadWriteLock {
public static void main(String[] args) throws Exception{
ReadWriteLock lock = new ReentrantReadWriteLock();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " wait to write lock ...");
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " get the write lock ... ===");
System.out.println("點擊任意鍵喚醒線程 ...");
Scanner sc = new Scanner(System.in);
sc.nextLine();
System.out.println(Thread.currentThread().getName() + " ready to unlock write ...");
lock.writeLock().unlock();
}).start();
Thread.sleep(5000);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " wait to write lock ... ");
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " get the write lock ... ===");
System.out.println(Thread.currentThread().getName() + " ready to write unlock ...");
lock.writeLock().unlock();
}).start();
}
}
輸出:
Thread-0 wait to write lock ...
Thread-0 get the write lock ... ===
點擊任意鍵喚醒線程 ...
Thread-1 wait to write lock ...
Thread-0 ready to unlock write ...
Thread-1 get the write lock ... ===
Thread-1 ready to write unlock ...
場景七:thread1 在 write ,thread1 準備來 read (測試降級)
代碼:
public class _14_07_TestReadWriteLock {
public static void main(String[] args) throws Exception{
new Thread(() -> {
ReadWriteLock lock = new ReentrantReadWriteLock();
System.out.println(Thread.currentThread().getName() + " wait to write lock ...");
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " get the write lock ... ===");
System.out.println(Thread.currentThread().getName() + " wait to read lock ...");
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " get the read lock ... ===");
System.out.println(Thread.currentThread().getName() + " ready to unlock read...");
lock.readLock().unlock();
System.out.println(Thread.currentThread().getName() + " ready to unlock write ...");
lock.writeLock().unlock();
}).start();
}
}
輸出:
Thread-0 wait to write lock ...
Thread-0 get the write lock ... ===
Thread-0 wait to read lock ...
Thread-0 get the read lock ... ===
Thread-0 ready to unlock read...
Thread-0 ready to unlock write ...
看下圖: 在 write鎖被持有,並且getExclusiveOwnerThread() == current的情況下,順利拿到 read 鎖。
=== 點擊查看top目錄 ===
場景八:thread1 在 write, thread再次 write (測試write鎖重入)
代碼
// 場景八:thread1 在 write, thread再次 write (測試write鎖重入)
public class _14_08_TestReadWriteLock {
public static void main(String[] args) throws Exception {
new Thread(() -> {
ReadWriteLock lock = new ReentrantReadWriteLock();
System.out.println(Thread.currentThread().getName() + " wait to write lock ...");
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " get the write lock ... ===");
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " get the write lock2 ... ===");
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " get the write lock3 ... ===");
System.out.println(Thread.currentThread().getName() + " ready to write unlock ...");
lock.writeLock().unlock();
System.out.println(Thread.currentThread().getName() + " ready to write unlock2 ...");
lock.writeLock().unlock();
System.out.println(Thread.currentThread().getName() + " ready to write unlock3 ...");
lock.writeLock().unlock();
}).start();
}
}
輸出:
Thread-0 wait to write lock ...
Thread-0 get the write lock ... ===
Thread-0 get the write lock2 ... ===
Thread-0 get the write lock3 ... ===
Thread-0 ready to write unlock ...
Thread-0 ready to write unlock2 ...
Thread-0 ready to write unlock3 ...
六、各個場景的分析結論:
- thread1 拿到read鎖的時候,thread2想read可以。(共享鎖可以共享)
- thread1 拿到read鎖的時候,thread1想write不行。(鎖無法升級)
- thread1 拿到read鎖的時候,thread2想write不行。
- thread1 拿到read鎖的時候,thread1想再次read可以。 (重入ok)
- thread1 拿到write鎖的時候,thread2想read不行。(獨佔鎖不允許共享)
- thread1 拿到write鎖的時候,thread2 想 write不行。(獨佔鎖不允許共享)
- thread1 拿到write鎖的時候,thread1想read可以。(鎖降級了)
- thread1 拿到write鎖的時候,thread1想write可以。 (重入ok)
所以:
- 一個線程要想同時持有寫鎖和讀鎖,必須先獲取寫鎖再獲取讀鎖;
- 寫鎖可以“降級”爲讀鎖;讀鎖不能“升級”爲寫鎖。
=== 點擊查看top目錄 ===
七、write 鎖釋放 tryRelease
protected final boolean ReentrantReadWriteLock.Sync#tryRelease(int releases) {
//若鎖的持有者不是當前線程,拋出異常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//寫鎖的新線程數,正常-1
int nextc = getState() - releases;
//如果獨佔模式重入數爲0了,說明獨佔模式write鎖被釋放
boolean free = exclusiveCount(nextc) == 0;
if (free)
//若write鎖的新線程數爲0,則將鎖的持有者設置爲null
setExclusiveOwnerThread(null);
//設置寫鎖的新線程數
//不管獨佔模式是否被釋放,更新獨佔重入數
setState(nextc);
return free;
}
=== 點擊查看top目錄 ===
八、read 鎖釋放 tryReleaseShared
protected final boolean tryReleaseShared(int unused) {
// 獲取當前線程
Thread current = Thread.currentThread();
if (firstReader == current) { // 當前線程爲第一個讀線程
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1) // read 線程佔用的資源數爲1
firstReader = null;
else // 減少佔用的資源
firstReaderHoldCount--;
} else { // 當前線程不爲第一個讀線程
// 獲取緩存的計數器
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) // 計數器爲空或者計數器的tid不爲當前正在運行的線程的tid
// 獲取當前線程對應的計數器
rh = readHolds.get();
// 獲取計數
int count = rh.count;
if (count <= 1) { // 計數小於等於1
// 移除
readHolds.remove();
if (count <= 0) // 計數小於等於0,拋出異常
throw unmatchedUnlockException();
}
// 減少計數
--rh.count;
}
for (;;) { // 無限循環
// 獲取狀態
int c = getState();
// 獲取狀態
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc)) // 比較並進行設置
return nextc == 0;
}
}
=== 點擊查看top目錄 ===
九、結論
而readwrite鎖有以下三個重要的特性:
(1)公平選擇性:支持非公平(默認)和公平的鎖獲取方式,吞吐量還是非公平優於公平。
(2)重進入:讀鎖和寫鎖都支持線程重進入。
(3)鎖降級:遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序,寫鎖能夠降級成爲讀鎖
=== 點擊查看top目錄 ===
十、番外篇
下一章節:【線程】線程八鎖與Synchronzied內部原理(十二)
上一章節:【線程】ReentrantLock 內部公平鎖與非公平鎖實現 (十)