直接去了线程Thread-0的信息,可以看到线程处于writeLock.lock()方法中,一直在等待唤醒。
首先这一切发生都是在a、b、c线程获取到读锁之后,a再次获取写锁导致死锁发生。所以我们假定目前ReentrantReadWriteLock中只有共享锁,而接着a尝试在有读锁的时候获取写锁,来看写锁的获取过程。
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }写锁会尝试限制性tryAcquire,如果失败的话就会进入队列中,所以先看tryAcquire这个方法
protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; }tryAcquire的方法比较简单,可以看出先判断当前有无线程拿到锁,就是直接判断state是否为0即可,因为前面已经有a、b、c三个线程获取到了资源,所以c不为0;同时因为当前没有写锁被获取过,所以exclusiveCount(c)返回结果应该也是为false,所以在if (w == 0 || current != getExclusiveOwnerThread()),这个条件应该是直接通过并且整个方法返回false。
因为tryAcquire的返回结果为false,所以我们来看acuqireQueued整个方法(addWaiter方法就是创建一个EXCLUSIVE的节点并且入队)
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }因为当前只有a尝试获取写锁,并且因为失败入队,所以当前队列只有一个虚头节点,以及准备获取写锁的线程a(从这里也可以看出尽管是同一个线程,但是写锁和读锁也是分开判断的)。
这里tryAcquire在公平锁和非公平锁有两种不同的实现:
// 非公平锁 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }在非公平锁中,分两种情况:
1)直接尝试CAS,失败则返回false;
2)当前线程已经获取过写锁了,所以直接重入即可。
// 公平锁 protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; }而在公平锁中,先判断是否有其他线程获取过资源,然后再判断是否有线程拿过写锁,很明显这里会在if (w == 0 || current != getExclusiveOwnerThread())进入,并返回结果false。
所以在已有多数线程获取读锁的情况下,其中一个线程重复获取写锁会失败,并在acuqireQueued这个方法中,进入shouldParkFailedAcquire进行判断。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) return true; if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }shouldParkAfterFailedAcquire 这个方法不陌生了,虽然刚开始创建的头节点wai'tStatus=0,但是架不住acuqireQueued外面有一个死循环,所以会在tryAcquire获取失败的时候继续进来,并且返回结果true,并最终进入parkAndCheckInterrupt自行挂起。
问题就是在这,线程a获取写锁导致线程被挂起,同样也挂起了前面a获取的读锁;虽然b、和c释放了读锁,因为a一直没法释放读锁导致a的写锁一直无法迟迟等待a自己释放读锁,最终陷入死锁。下面是读锁尝试释放所的代码
protected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); if (firstReader == current) { // assert firstReaderHoldCount > 0; if (firstReaderHoldCount == 1) 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; // a读锁没有释放,所以nextc仍然不等于0 if (compareAndSetState(c, nextc)) return nextc == 0; } }不要在获取了读锁的情况下在获取写锁,但是在拿到写锁的时候可以拿读锁(锁降级)。