package snippet;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
public class Test {
public static void main(String[] args) throws InterruptedException {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
new Thread(new Write(lock)).start();//写,持有锁5s
new Thread(new Read(lock)).start();//读
Thread.currentThread().sleep(1000);//等待一秒,当前等待队列:读
System.out.println("当前等待队列长度:"+lock.getQueueLength());
new Thread(new Read(lock)).start();//读
Thread.currentThread().sleep(1000);//等待一秒,当前等待队列:读<--读
System.out.println("当前等待队列长度:"+lock.getQueueLength());
new Thread(new Write(lock)).start();//写
Thread.currentThread().sleep(1000);//等待一秒,当前等待队列:读<--读<--写
System.out.println("当前等待队列长度:"+lock.getQueueLength());
new Thread(new Read(lock)).start();//读
new Thread(new Read(lock)).start();//读
new Thread(new Read(lock)).start();//读
Thread.currentThread().sleep(1000);//等待一秒,当前等待队列:读<--读<--写<--读<--读<--读
System.out.println("当前等待队列长度:"+lock.getQueueLength());
Thread.currentThread().sleep(5000);//等待五秒
System.out.println("最后一个读");
System.out.println("当前等待队列长度:"+lock.getQueueLength());
new Thread(new Read(lock)).start();//读
Thread.currentThread().sleep(1000);//等待一秒
System.out.println("当前等待队列长度:"+lock.getQueueLength());
}
}
class Read implements Runnable{
ReentrantReadWriteLock lock;
public Read(ReentrantReadWriteLock lock){
this.lock = lock;
}
@Override
public void run() {
ReadLock readLock = lock.readLock();
readLock.lock();
System.err.println("读:"+Thread.currentThread().getId());
try {
Thread.currentThread().sleep(15000);
System.out.println("持有读锁数量:"+lock.getReadLockCount());
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
readLock.unlock();
}
}
}
class Write implements Runnable{
ReentrantReadWriteLock lock;
public Write(ReentrantReadWriteLock lock){
this.lock = lock;
}
@Override
public void run() {
WriteLock writeLock = lock.writeLock();
writeLock.lock();
System.out.println("写:"+Thread.currentThread().getId());
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
writeLock.unlock();
System.out.println("写释放:"+Thread.currentThread().getId());
}
}
}
输出
写:9
当前等待队列长度:1
当前等待队列长度:2
当前等待队列长度:3
当前等待队列长度:6
写释放:9
读:10
读:11
最后一个读
当前等待队列长度:4
当前等待队列长度:5
持有读锁数量:2
持有读锁数量:2
写:12
写释放:12
读:13
读:15
读:14
读:16
持有读锁数量:4
持有读锁数量:4
持有读锁数量:4
持有读锁数量:4
分析:
从 “当前等待队列长度:6” 输出可以知道,当前等待队列为:读<–读<–写<–读<–读<–读,当写锁释放后,等待队列前两个读线程依次获得了读锁,而等待队列上写线程之后的读线程并不能也同时获得锁,而需要等到前两个读锁释放锁,之后写线程获取并释放锁之后,才能获取到锁。
从“最后一个读”且后面是“当前等待队列长度:4,当前等待队列长度:5”的输出可以知道,虽然当前读锁被占有,但是最后一个新进来的还未入队的读线程不能获取读锁,而会进入同步队列进行等待。
结论:
当读锁被线程持有时,该读锁后续的读线程都可以获取到读锁,当如果有一个写线程进来,则写线程后边再进来的读线程是无法获得到锁的,而需要等待写线程出队之后才能获得到锁。
如果当前读锁被持有,然后等待队列上边有写线程在等待,这个时候进来一个新的读线程,该读线程跟已经排在写线程之后的读线程一样,会进入队列进行阻塞,而不会获取读锁。
源码分析
结论1
/**
* 共享式不响应终端的获取锁
*/
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
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) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null;
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;
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
//从这个判断可以看出,当读锁获取成功之后,如果下一个节点是读线程,则唤醒它,之后被唤醒的读线程获取锁后会继续唤醒下个读线程,产生多米诺骨牌效应;而如果下一个节点是写线程,则不会唤醒
if (s == null || s.isShared())
doReleaseShared();
}
}
结论2
//ReentrantReadWriteLock.Sync中的方法
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 != current.getId())
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
//判断当前等待队列是否有写线程,如果返回true,避免写线程的饥饿等待
return apparentlyFirstQueuedIsExclusive();
}
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}