死磕 java同步系列之ReentrantReadWriteLock源码解析 (3)

如下图,ABC三个节点各获取了一次共享锁,三者释放的顺序分别为ACB,那么最后B释放共享锁的时候tryReleaseShared()才会返回true,进而才会唤醒下一个节点D。

ReentrantReadWriteLock2

WriteLock.lock() // java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock.lock() public void lock() { sync.acquire(1); } // java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire() public final void acquire(int arg) { // 先尝试获取锁 // 如果失败,则会进入队列中排队,后面的逻辑跟ReentrantLock一模一样了 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // java.util.concurrent.locks.ReentrantReadWriteLock.Sync.tryAcquire() 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,说明共享锁被获取的次数不为0 // 这句话整个的意思就是 // 如果共享锁被获取的次数不为0,或者被其它线程获取了互斥锁(写锁) // 那么就返回false,获取写锁失败 if (w == 0 || current != getExclusiveOwnerThread()) return false; // 溢出检测 if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // 到这里说明当前线程已经获取过写锁,这里是重入了,直接把state加1即可 setState(c + acquires); // 获取写锁成功 return true; } // 如果c等于0,就尝试更新state的值(非公平模式writerShouldBlock()返回false) // 如果失败了,说明获取写锁失败,返回false // 如果成功了,说明获取写锁成功,把自己设置为占有者,并返回true if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; } // 获取写锁失败了后面的逻辑跟ReentrantLock是一致的,进入队列排队,这里就不列源码了

写锁获取的过程大致如下:

(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)如果同一个线程先获取读锁,再获取写锁会怎样?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwffd.html