前言
首先明确一点,ReentrantReadWriteLock 指的并不是一个锁,而是管理着读锁&写锁这样的一组锁,读锁是可共享的(shared),写锁是独占的。这么设计是有原因的,读操作之前互不影响,而写操作需要确保对于读操作是完整且结果可见的。其中ReentrantReadWriteLock实现上跟ReentrantLock类似,可选择公平&非公平,可重入等,里面持有的readlock和writelock都持有继承自AQS的Sync,依赖于AQS实现对应的功能。
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
使用
使用上,我们可以定义一个ReentrantLock来获取对应的ReadLock及WriteLock完成对应的功能,下面是一个简单的demo,可以执行一下,看一下具体的效果。
demo
public class ReadWriteLockDemo {
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
public static void main(String[] args) {
Data data = new Data();
Worker t1 = new Worker(data, false);
//写
Worker t2 = new Worker(data, true);
//读
Worker t3 = new Worker(data, true);
//读
t1.start();
t2.start();
t3.start();
}
static class Worker extends Thread {
Data data;
Boolean read;
public Worker(Data data, Boolean read) {
this.data = data;
this.read = read;
}
public void run() {
if (read)
data.read(); else
data.write();
}
}
static class Data {
ReadWriteLock lock = new ReentrantReadWriteLock();
Lock read = lock.readLock();
Lock write = lock.writeLock();
public void write() {
try {
Thread.sleep(2000);
//
}
catch (Exception e) {
}
write.lock();
System.out.println(Thread.currentThread() + " write:begin "
+ sdf.format(new Date()));
try {
Thread.sleep(5000);
//
}
catch (Exception e) {
}
finally {
System.out.println(Thread.currentThread() + " write:end "
+ sdf.format(new Date()));
write.unlock();
}
}
public int read() {
read.lock();
System.out.println(Thread.currentThread()+ " read :begin "
+ sdf.format(new Date()));
try {
Thread.sleep(5000);
//
}
catch (Exception e) {
}
finally {
System.out.println(Thread.currentThread() + " read :end "
+ sdf.format(new Date()));
read.unlock();
}
return 1;
}
}
}
说原理之前先说结论:
取锁:
1、如果当前没有写锁或读锁时,第一个获取锁的线程都会成功。
2、如果当前已经有了读锁,那么这时获取写锁将失败,获取读锁有可能成功也有可能失败。
3、如果当前已经有了写锁,那么这时获取读锁或写锁,如果线程相同(可重入),那么成功
释放:
1、如果当前是写锁被占有了,只有当写锁的标示数据降为0时才认为释放成功,否则失败。因为只要有写锁,那么除了占有写锁的那个线程,其他线程即不可以获得锁。
2、如果当前是读锁被占有了,那么只有在写锁的个数为0时才认为释放成功。
原理
与Reentrant相同的实现部分不做介绍,主要说不同之处:
读写锁里面存在这样几个静态内部类:
Sync entends AQS(内含:ThreadLocalHoldCounter extends ThreadLocal、HoldCounter
)、FairLock extends Sync
、NonFairLock extends Sync
、ReadLock
、WriteLoc
、(其中ReadLock、WriteLock 分别持有一个Sync)在Sync中维护了Read和Write的使用之间的关系,并且Read、Write全部依赖于Sync中的逻辑,内部类中并没有具体的逻辑实现,所以说看整个ReentrantReadWriteLock 看Sync的核心实现就OK了。
Sync
内部state分为两个无符号的short,读锁锁定的高16位,低16位为写锁锁定,以这个为基础维护读锁及写锁之间的关系:
并且内部维持这些静态属性:
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
WriteLock
state变量的低16位,当state低16位为0,表示当前写锁没有被占有,反之表示写锁被某个写线程占有。
占用条件&过程
1、读锁未被占用(state高16位为0) ,写锁未被占用(state低16位为0)或 占用写锁的线程是当前线程
2、writerShouldBlock()方法返回false,即不阻塞写线程
3、写锁占有量小于最大值(2^16 -1),否则抛出Error("Maximum lock count exceeded")
4、通过CAS竞争将写锁状态+1(将state低16位同步+1)
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;
}
释放过程:
写锁的释放过程是state低16位同步递减为0的过程,当state的高16位都为0表示写锁释放完毕,唤醒head节点下一个SIGNAL状态的节点的线程。如果该写锁占有线程未释放写锁前还占用了读锁,那么写锁释放后该线程就完全转换成了读锁的持有线程。
调用的AQS 原生方法:
public void unlock() {
sync.release(1);
}
ReadLock
当state的高16位等于0,表示当前读锁未被占有;当state的高16位大于0,表示当前读锁可能被一个或多个线程占有,多于一个占有读锁的线程,允许重入。
占用条件&过程:
1、当前的写锁未被占有(state低16位为0) 或者当前线程是写锁占有的线程
2、readerShouldBlock()方法返回false
3、当前读锁占有量小于最大值(2^16 -1)
4、成功通过CAS操作将读锁占有量+1(AQS的state高16位同步加1)
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);
}
如果逻辑结果小于0,表示当前无条件获得,则将该线程加入阻塞队列(doAcquireShared)
释放过程:
读锁的释放过程即AQS的state高16位同步递减为0的过程,当state的高16位都为0表示读锁释放完毕,如果此时写锁状态为0(即该读锁不是写锁降级来的),唤醒head节点下一个SIGNAL状态的节点的线程。如果读锁的占有数不为0,表示读锁未完全释放。或者写锁的占有数不为0,表示释放的读锁是写锁降级来的。
public void unlock() {
sync.releaseShared(1);
}
看完ReentrantLock&ReentrantReadWriteLock,是不是发现满满的都是CAS和for ;;,这就是非阻塞的一种实现原理,有利有弊吧。
网友评论