并发编程12–ReentrantLock解锁流程和读写锁源码
-
{ w thread=t1} ^ | V { r thread=t2 ws=-1} <-> { shared thread=null prev=null next=null} // 比普通独占锁中的节点多了shared属性,下一个节点是shared继续唤醒,直到遇到exclusive ^ | V { r thread=t3 ws=-1} <-> { shared thread=null prev=null next=null} ^ | V { w thread=t4 ws=-1} <-> { exclusive thread=null prev=null next=null} ^ | V { r thread=t5}
写锁的上锁流程
-
package BingFaBianCheng.bingFaBianCheng12; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 写锁的上锁流程 */ @Slf4j(topic = "enjoy") public class RWLock2 { //读写锁 static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); static Lock r = rwl.readLock(); static Lock w = rwl.writeLock(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { w.lock(); try { log.debug("t1 写锁加锁成功"); } finally { w.unlock(); } }, "t1"); t1.start(); } }
写锁的上锁流程
-
// 写锁在加锁的时候要么锁没有被人持有则会成功,要么重入成功,其他的都失败 protected final boolean tryAcquire(int acquires) { /* * 1、获取当前线程 */ Thread current = Thread.currentThread(); // 获取锁的状态 int c = getState(); // 因为读写锁是同一把锁(同一个对象) // 为了标识读写锁,把锁的前16位标识读锁的状态 // 锁的后16位标识写锁的状态 // 此处是获取写锁的状态 // int 4字节 32bit // 0000000000000000 0000000000000001 int w = exclusiveCount(c); // 表示有人上了锁(不知道是读锁还是写锁) // 1.首先判断锁是否自由 // 2.因为读写互斥、写写互斥,所以需要判断重入的类型(升级、降级) // 2.1降级是允许的 // 2.2升级是不允许的 if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) // 判断是否重入,同时需要判断重入的类型,所以首先需要知道当前锁的类型 // 1.w==0表明从来没上过写锁,只能是读锁 // 而当前自己是来加写锁,所以是锁升级,所以加锁失败 // 并且此处是写锁加锁,不可能出现锁降级的情况 // 2.||或 w!=0,说明加过写锁 // 判断是否是重入,如果不是重入则加锁失败 if (w == 0 || current != getExclusiveOwnerThread()) return false; // 表示是重入 // 把后十六位+1 > MAX_COUNT(2^16-1) // 但是这个判断条件基本不会true if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire // 没有超出重入限制,则把c+1 setState(c + acquires); return true; } // 如果正常情况下就是当前这个例子是第一次加锁 // 1.writerShouldBlock() 判断自己是否需要排队 // 如果是非公平锁直接返回false,不排队,直接抢锁 // 如果是公平锁,返回自己是否需要排队 // 2.|| !compareAndSetState(c, c + acquires) // 公平锁需要排队,则不执行上一句,也就是不去抢锁 if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; // 加锁成功,把当前持有锁的线程改成自己 setExclusiveOwnerThread(current); return true; }
-
公平锁情况,writerShouldBlock()什么时候需要排队?
-
排队说明队列中有人,但是c又等于0,不是矛盾了吗,其实不是,在高并发情况下,持有锁的线程刚好在代码执行到这里时释放了,因为释放锁的操作又不是原子操作。
-
在高并发情况下,这些情况是很常见的!
写锁上锁aqs状态
-
Node head -> { thread=null prev=null next=tail waitstatus=-1} ^ | V Node tail -> { thread=t2 pre=head next=null waitstatus=0} exclusiveOwnerThread:t1 state:r_w // 前十六位标识读锁,后十六位标识写锁
写锁解锁流程
-
public final boolean release(int arg) { // 如果解锁成功 if (tryRelease(arg)) { Node h = head; // ws=-1,表示有责任去唤醒下一个节点 if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
-
protected final boolean tryRelease(int releases) { // 判断这把锁是否有人持有 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases; // 计算后16位是否等于0 boolean free = exclusiveCount(nextc) == 0; if (free) setExclusiveOwnerThread(null); // 解锁成功 setState(nextc); return free; }
读锁
加锁流程
-
package BingFaBianCheng.bingFaBianCheng12; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 读锁的上锁流程 */ @Slf4j(topic = "enjoy") public class RWLock3 { //读写锁 static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); static Lock r = rwl.readLock(); static Lock w = rwl.writeLock(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { r.lock(); try { log.debug("t1 写锁加锁成功"); } finally { r.unlock(); } }, "t1"); t1.start(); } }
-
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } // 读锁加锁失败返回-1,成功则返回的数大于0 protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); // exclusiveCount(c) != 0拿后十六位,判断是否上了写锁 // 如果上了写锁,然后再继续判断---重入降级 // 如果是重入则一定是降级,如果不是重入则失败,因为读写互斥 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; // 继续往下执行有两种情况 // 1.没有上写锁 2.重入降级 int r = sharedCount(c);// 得到上锁的次数 // 假设恒定不需要排队,重入次数小于最大次数,并且加锁成功 if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { // 已经加锁成功了,为什么不直接返回? // r是加锁之前的值 // r==0表明则是第一次给这把锁加读锁 if (r == 0) { // firstReader就是一个局部变量Thread // 如果是第一个线程全局第一次加锁,则把这个线程赋值给firstReader firstReader = current; // 记录第一个线程的加锁次数 firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { // 相当于一个空的map(记录了线程id,重入次数) HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) // readHolds是threadLocal,初始化HoldCounter,并赋值给rh // cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } // 加锁成功 return 1; } return fullTryAcquireShared(current); } // 读锁是共享锁 Node head Node tail exclusiveOwnerThread:t1 state:6_0 // t1加5次,t2加1次,这里的读锁6次有很多种可能,还可能有更多别的线程 // aqs中没有办法存储每个线程的重入次数,可以用threadLocal来存储
场景1,t1第一次加写锁
-
Node head Node tail exclusiveOwnerThread:t1 state:1_0 firstReader:t1 firstReaderHoldCount:1
场景2,t1重入第二次加锁
-
Node head Node tail exclusiveOwnerThread:t1 state:2_0 firstReader:t1 firstReaderHoldCount:2
场景3,t1释放掉读锁,t2过来拿锁
-
Node head Node tail exclusiveOwnerThread:t2 state:1_0 firstReader:t2 firstReaderHoldCount:1
场景4,t2持有读锁,tn来拿锁,读读并发
-
Node head Node tail exclusiveOwnerThread:t2 state:1_0 firstReader:t2 firstReaderHoldCount:1 cachedHoldCounter.count:1 // 又一个变量,只记录了最后一个线程的重入次数 // 同时在tn它自己的threadLocalMap中也存了一份
场景5,t2持有读锁,tn来拿锁,tm来拿锁,读读并发
-
Node head Node tail exclusiveOwnerThread:t2 state:1_0 firstReader:t2 firstReaderHoldCount:1 cachedHoldCounter.count:1 // tm没来时,记录的是tn的获取锁次数 // tm来了之后更新为tm的获取锁次数 #tn_threadLocalMap:1 // tn的值存在tn的threadLocalMap中
读锁加锁失败,去排队
-
// 如果上面加锁失败了 private void doAcquireShared(int arg) { // new出来的node的标识是shared // 如果写成boolean变量就容易懂,这里相当于Node.Node 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) { 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); } }
场景6 t1是写锁,t2和t3是读锁,
-
ws:waitstatus nw:nextwaiter Node head ->{thread=null prev=null next=t2 ws=-1} ^ | V {读锁 thread=t2 pre=head next=t3 ws=-1 nw=SHARED} ^ | V Node tail ->{读锁 thread=t3 pre=t2 next=null ws=0 nw=SHARED} exclusiveOwnerThread:t1(写锁) state:2_1
解锁流程(此处读锁解锁实际是从LockSupport.lock阻塞的地方继续往下面执行的)
-
private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED); boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head) { // t2读锁阻塞被唤醒后获得锁 int r = tryAcquireShared(arg); if (r >= 0) { // 继续往后传播,读锁持续获得锁 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); } }
-
private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below // 把自己设置成头结点 setHead(node); if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { // 取出t3 Node s = node.next; if (s == null || s.isShared()) // 把t3叫醒,依然是LockSupport.lock阻塞的地方继续往下面执行 doReleaseShared(); } }
-
private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }
读读并发原理
- 加锁的过程>0,依然可以加锁成功
- 如果唤醒的是一把读锁,会循环往下面判断是否是共享锁(nextwaiter==SHARED),直到遇到的不是读锁