今天礼拜六,晴,加班,真好!
今天来研究一下可重入读写锁-ReentrantReadWriteLock。
首先要明确的一点是,前面研究的可重入锁ReentrantLock,其实是一个互斥锁,保证在同一时刻只有一个线程获取资源,无论是写写,读读,读写任何场景都是互斥的。这种互斥锁在常见的高并发场景下会显现出糟糕的低吞吐量问题。
事实上,在高并发的实际场景下,读的频率远高于写,而且读本身不会对数据进行任何修改,只是读取,因此希望对读的操作进行共享,而对写操作进行独占。
这就是读写锁的作用。
它的特性就是:一个资源可以被多个读操作同时访问,或者被一个写操作独占。但是两者不能同时访问。
ReentrantReadWriteLock并没有继承自ReentrantLock 也 并不实现Lock接口,而是实现独有的ReadWriteLock接口。
此接口下仅有两个方法
//获取读锁
Lock readLock();
//获取写锁
Lock writeLock();
如果创建一个读写锁,简单看下构造器
//默认创建非公平读写锁
public ReentrantReadWriteLock() {
this(false);
}
//也可以通过指定fair 为true创建一个公平的读写锁
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
//至于这里的Sync 继承自AQS,FairSync和NonFairSync都继承自Sync,其实现与可重入锁中实现差不多。不赘述了、
从ReentrantLock的具体实现中得知,其独占性和重入性都是通过对AQS内state变量的操作实现的。
ReentrantReadWriteLock中由于需要实现读和写两个操作,把state这个int变量分为高16位和低16位。
高16位为读锁占用量,低16位为写锁占用量。
读锁lock()的方法下,关键方法如下
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 如果是独占锁(写锁)并且并不是当前线程独占,直接返回-1,获取读锁失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
// 三个判断
//1、读锁是否需要被阻塞:非公平锁不需要阻塞、公平锁下,如果在等待队列中存在其他线程,则返回true,
//否则返回false,往下走
//2、获取读锁的数量小于最大数量
//3、CAS操作将读锁占有量+1
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//第一次获取读锁
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
//可重入获取读锁,对应记录+1
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
//rh是记录最后一个
HoldCounter rh = cachedHoldCounter;
//如果并不是,就从ThreadLoccal取并设置为最后一个
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//这里的代码同上基本差不多,不赘述
return fullTryAcquireShared(current);
}
当获取读锁失败,就进入AQS等待队列,在判断前方节点为SIGNAL状态后进入等待状态,等待被唤醒后竞争资源。
读锁unLock()方法
一目了然 没啥好说的。。
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;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
写锁lock()方法:
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// 是独占锁且不说当前线程直接返回false
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;
}
写锁unlock方法
很简单,不说了
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
public class RRWLTest {
public static void main(String[] args) {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
CountDownLatch latch = new CountDownLatch(1);
new Thread(new Test(readLock, null, 0, "A", latch)).start();
new Thread(new Test(null, writeLock, 2000, "B", latch)).start();
new Thread(new Test(readLock, null, 0, "C", latch)).start();
new Thread(new Test(null, writeLock, -500, "D", latch)).start();
new Thread(new Test(readLock, null, 0, "E", latch)).start();
new Thread(new Test(readLock, null, 0, "F", latch)).start();
new Thread(new Test(null, writeLock, 600, "G", latch)).start();
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
latch.countDown();
}
static class Test implements Runnable{
public static volatile int account =0;
private ReentrantReadWriteLock.ReadLock readLock;
private ReentrantReadWriteLock.WriteLock writeLock;
private int money;
private String name;
private CountDownLatch latch;
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(null!=readLock) {
readLock.lock();
System.out.println(String.format("name:%s,查询余额:%s", name,account));
readLock.unlock();
}
else {
writeLock.lock();
account+=money;
if(money>0) {
System.out.println(String.format("name:%s,存款:%s,余额:%s", name,money,account));
}else {
System.out.println(String.format("name:%s,取款:%s,余额:%s", name,-money,account));
}
writeLock.unlock();
}
}
public Test(ReadLock readLock, WriteLock writeLock, int money, String name, CountDownLatch latch) {
super();
this.readLock = readLock;
this.writeLock = writeLock;
this.money = money;
this.name = name;
this.latch = latch;
}
}
}
运行结果:
name:B,存款:2000,余额:2000
name:D,取款:500,余额:1500
name:G,存款:600,余额:2100
name:C,查询余额:2100
name:F,查询余额:2100
name:A,查询余额:2100
name:E,查询余额:2100
明天礼拜天,可以睡懒觉了