如下图,ABC三个节点各获取了一次共享锁,三者释放的顺序分别为ACB,那么最后B释放共享锁的时候tryReleaseShared()才会返回true,进而才会唤醒下一个节点D。
写锁获取的过程大致如下:
(1)尝试获取锁;
(2)如果有读者占有着读锁,尝试获取写锁失败;
(3)如果有其它线程占有着写锁,尝试获取写锁失败;
(4)如果是当前线程占有着写锁,尝试获取写锁成功,state值加1;
(5)如果没有线程占有着锁(state==0),当前线程尝试更新state的值,成功了表示尝试获取锁成功,否则失败;
(6)尝试获取锁失败以后,进入队列排队,等待被唤醒;
(7)后续逻辑跟ReentrantLock是一致;
WriteLock.unlock() // java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock.unlock() public void unlock() { sync.release(1); } //java.util.concurrent.locks.AbstractQueuedSynchronizer.release() public final boolean release(int arg) { // 如果尝试释放锁成功(完全释放锁) // 就尝试唤醒下一个节点 if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } // java.util.concurrent.locks.ReentrantReadWriteLock.Sync.tryRelease() protected final boolean tryRelease(int releases) { // 如果写锁不是当前线程占有着,抛出异常 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); // 状态变量的值减1 int nextc = getState() - releases; // 是否完全释放锁 boolean free = exclusiveCount(nextc) == 0; if (free) setExclusiveOwnerThread(null); // 设置状态变量的值 setState(nextc); // 如果完全释放了写锁,返回true return free; }写锁释放的过程大致为:
(1)先尝试释放锁,即状态变量state的值减1;
(2)如果减为0了,说明完全释放了锁;
(3)完全释放了锁才唤醒下一个等待的节点;
总结(1)ReentrantReadWriteLock采用读写锁的思想,能提高并发的吞吐量;
(2)读锁使用的是共享锁,多个读锁可以一起获取锁,互相不会影响,即读读不互斥;
(3)读写、写读和写写是会互斥的,前者占有着锁,后者需要进入AQS队列中排队;
(4)多个连续的读线程是一个接着一个被唤醒的,而不是一次性唤醒所有读线程;
(5)只有多个读锁都完全释放了才会唤醒下一个写线程;
(6)只有写锁完全释放了才会唤醒下一个等待者,这个等待者有可能是读线程,也可能是写线程;
彩蛋(1)如果同一个线程先获取读锁,再获取写锁会怎样?