目录
前言
众所周知,ReentrantReadWriteLock是读写锁,它拥有独占锁和共享锁两种模型。
如果是读操作:上共享锁,因为是可以同时访问的;
而如果是写操作:上独占锁,写写或者读写都得上锁,等它完成释放资源后才能其他的访问;
其实ReentrantLock与之一样,ReentrantWriteLock是一把锁
写锁的获取
其实会尝试获取三次,这个for循环里会判断两次,如果还是没有获取到锁,那么就会被park进入阻塞;
public void lock() {
sync.acquire(1);
}
与ReentrantLock一样的流程,先尝试获取锁,如果失败将线程封装为Node,加入到阻塞队列,调用acquireQueued方法实现阻塞当前线程(acquireQueued方法中会得到当前节点的前序节点,然后再次判断是否拿到锁资源,没拿到就会进入阻塞park掉(不对,会再次循环一次如果还没有才会park));
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
//获取当前同步状态
int c = getState();
//获取写锁状态(w>0表示已经有线程获取写锁)
int w = exclusiveCount(c);
// 因为state分了高16位与低16位给到读写锁,所以只能说明获取了锁
if (c != 0) {
//1.写锁=0表示加的读锁,因为读写互斥,直接返回false,2.加了写锁,看是不是自己加的,如果不是,就获取失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//不能超过最大值,重入次数
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//表示发生写重入返回true
setState(c + acquires);
return true;
}
// 说明没有加锁c=0,别的线程加锁(writeShouldBlock会判断是否是公平模式,若为公平就会对队列进行判断,看后继是否有节点,因为公平模式是轮流来的)
if (writerShouldBlock() ||
//cas操作将state改为1
!compareAndSetState(c, c + acquires))
return false;
//当前线程为拥有者
setExclusiveOwnerThread(current);
return true;
}
读锁获取
这些都是AQS同步器中的方法
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
// tryAcquireShared会判断是否有写锁占据
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
-1表示失败,0表示成功但是后续节点不会被唤醒,正数表示成功并且后续节点可以被唤醒;
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
对比与之前的ReentrantLock,线程被封装的节点是共享状态的而不是独占状态;
private void doAcquireShared(int arg) {
// 包装节点,这里包装的用的是共享节点(两个:一个是头节点,一个是封装线程的节点)
final Node node = addWaiter(Node.SHARED);
boolean interrupted = false;
try {
// 进行遍历,跟之前的ReentrantLock有相似之处
for (;;) {
// 得到前序节点 :判断是否是头节点
final Node p = node.predecessor();
//如果是头节点,说明后续节点也就是当前node老二有资格被唤醒
if (p == head) {
//会进行一次尝试,看读锁是否加锁成功,成功返回1(tryAcquiredShared是读写锁中实现加锁的方法)
int r = tryAcquireShared(arg);
if (r >= 0) {
//因为此时老二已经被唤醒了,所以老二此时要做头节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
return;
}
}
//第一次调用shouldParkAfterFailedAcquire目的是:将前序节点状态设置为-1,方便唤醒当前节点,此时还没有park,第二次如果尝试还是没有获取锁,就会park;
if (shouldParkAfterFailedAcquire(p, node))
//parkAndCheckInterrupt()执行的是一个唤醒流程
interrupted |= parkAndCheckInterrupt();
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
} finally {
if (interrupted)
selfInterrupt();
}
}
重新设置头节点
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) {
//得到下一个节点
Node s = node.next;
//判断节点是否为null,是否是共享节点,如果是共享节点的话,就会被唤醒,他是会被连着一起的,但是如果是独占锁,就不会执行器doReleaseShared
if (s == null || s.isShared())
doReleaseShared();
}
}
tryReleasedShared:将当前节点后续节点状态进行修改(-1——>0)
目的:如果说后续节点状态为-1,那么如果其他线程过来就会对其进行干扰,因为头节点也为-1了,老二都没被唤醒,其他线程过来就会造成干扰,让老二唤醒它们;——>然后里面有个unparkSuccessor()方法
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
//cas操作将状态进行修改
if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
continue; // loop to recheck cases
//唤醒,状态回变为-1
unparkSuccessor(h);
}
else if (ws == 0 &&
!h.compareAndSetWaitStatus(0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
t2,t3线程进行竞争,t2因为加的是读锁所以会是shared,t4加的是写锁,所以是EX;
问:当你如果是不公平模式下,写锁产生了饥饿现象怎么办?
为什么会产生饥饿现象?
因为你不公平模式,所以会有个竞争,如果在队列中写锁在第一个节点,读锁在写锁后面,
读锁为重入锁的话,那么就会导致写锁很难被获取到,就会产生一个饥饿现象;
目前思路:
可以设置为公平模式;
写锁unlcok
AQS中的抽象方法 :
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//进行唤醒,让下一个节点唤醒,唤醒t2线程,之前的t1线程在tryRelease被释放
unparkSuccessor(h);
return true;
}
return false;
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
对应在读写锁中实现
tryRelease:目的就是将当前锁进行释放,比如t1线程
protected final boolean tryRelease(int releases) {//1
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//原来线程状态-1
int nextc = getState() - releases;
//进行判断,是否减为0
boolean free = exclusiveCount(nextc) == 0;
//当-为0
if (free)
// 将当前锁资源拥有者设置为null
setExclusiveOwnerThread(null);
//并且重新设置线程状态
setState(nextc);
return free;
}
读写锁中实现读锁获取的方法tryAcquireShared()——>继承了AQS,实现了它的tryAcquireShared方法
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
读取锁unlock()
读写锁中调用同步器中releaseShared()方法
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
然后来看看读写锁中的释放锁tryReleaseShared:
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.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;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}