ReentrantReadWriteLock介绍
ReentrantReadWriteLock是读写锁,它维护了一对相关的锁读取锁
和写入锁
,一个用于读操作,另一个用于写操作
读取锁
:用于只读操作,这它是“共享锁“,能同时被多个线程获取.
写入锁
:用于写入操作,它是“独占锁”,写入锁只能被一个线程锁获取。
允许多个读线程同时读并阻塞写线程,多个写线程只有一个写线程写,并阻塞读线程
在了解了AQS底层后和ReentrantLock是如何通过AQS实现锁后,那么接线来看一下读写锁是如何通过AQS实现的
/**
* 多个线程读,一个线程写
*/
public class ReadWLock {
private ReadWriteLock lock = new ReentrantReadWriteLock();
private Lock re = lock.readLock();
private Lock wr = lock.writeLock();
private List<String> list ;
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public void set(String s){
try{
wr.lock();
list.add(s);
System.out.println("wr,listsize"+list.size());
}finally {
wr.unlock();
}
}
public String get(){
try{
re.lock();
if(list.size()>0){
String s = list.remove(list.size()-1);
System.out.println("re,listsize"+list.size());
return s;
}
return "";
}finally {
re.unlock();
}
}
}
二、ReentrantReadWriteLock基于AQS实现
1、ReentrantReadWriteLock结构
从这个结构可以看出读写锁比ReentrantLock多了2个ReadLock、 WriteLock类
ReadLock WriteLock这2个类都持有Sync类对象,同时也实现了流程方法,但实际这些流程方法都是调用了Sync类的方法
2 、 调用
private ReadWriteLock lock = new ReentrantReadWriteLock();
private Lock re = lock.readLock();
private Lock wr = lock.writeLock();
private List<String> list ;
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public void set(String s){
try{
wr.lock();
list.add(s);
System.out.println("wr,listsize"+list.size());
}finally {
wr.unlock();
}
}
public String get(){
try{
re.lock();
if(list.size()>0){
String s = list.remove(list.size()-1);
System.out.println("re,listsize"+list.size());
return s;
}
return "";
}finally {
re.unlock();
}
}
3、写锁调用
写线程获取锁方法lock():
public void lock() {
sync.acquire(1);//调用sync获取锁的方法
}
public final void acquire(int arg) {
//获取锁成功返回,失败加入同步队列 阻塞,等待锁释放被唤醒
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
写锁中tryAcquire是流程方法,需要在读写锁中实现:
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();//获锁状态标志位
int w = exclusiveCount(c);//取出锁状态的低16位-》记录了写锁个数
if (c != 0) {//锁状态不为0,说明锁被读线程或写线程占有
//再判断写锁是否被占有或已被占有被不是当前线程占有
//返回false,写线程没有获取锁成功,
//这里current != getExclusiveOwnerThread()
//判断当前线程如果占有锁那么也可锁取锁,因为锁是可重入的
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//定锁个数超过最大值,抛出异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//写线程获得锁,并将获取锁次数加1
setState(c + acquires);
return true;
}
//锁没有被占用,cas更新锁状态;更新失败返回,
//加入到同步队列中后,调用acquireQueued方法被阻塞,
//等待锁释放后被唤醒
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//设置当前线程为占有锁的线程
setExclusiveOwnerThread(current);
return true;
}
可以看出读写锁中写锁实现方式和ReentrantLock实现方式很像,只是这里是用state的低16位记录了锁的获取次数
4、写锁释放
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {//释放锁
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//唤醒后继节点
return true;
}
return false;
}
//这里没有加锁,是因为当前线程是持有锁的线程,它去释放锁
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases; //锁次数-1
boolean free = exclusiveCount(nextc) == 0;//设置到低16位中
//如果获取锁次数全部释放完,设置当前持有锁线程为Null
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
5、读锁调用
读线程获取锁方法lock():
public void lock() {
sync.acquireShared(1);//调用了共享锁的获取方式
}
public final void acquireShared(int arg) {
//尝试获取锁,流程方法在读写锁中实现
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
读锁中tryAcquireShared实现:
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
//取现出低16位不为0(写锁被占用)
//并且不是当前线程不是占有锁线程 返回,
//因为有写线程在写入,那么读线程不允计操作
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//获取高16位-》记录了获取读锁的线程个数
int r = sharedCount(c);
//如果读不应该阻塞并且读锁的个数小于65535,
//并且可以成功更新状态值,成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
//没有写线程占锁,那么获锁读锁线程个数加1
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {//之前读线程个数为0
//将当前线程记录为第一个读线程
firstReader = current;
//第1个读线程锁取个数赋值1
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//第一个读线程又获取锁
//第1个读线程锁取个数+1
firstReaderHoldCount++;
} else {
//当前线程不是第1个读线程,
//那么它的threadLocal将本线程获取锁次数+1
HoldCounter rh = cachedHoldCounter;
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);
}
6、读锁释放
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//尝试释放读锁
doReleaseShared();//调用AQS释放共享锁
return true;
}
return false;
}
读写锁对tryReleaseShared实现
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {//当前线程是第1个读线程
if (firstReaderHoldCount == 1)//只获取过1次
firstReader = null; //记录第1个读线程为Null
else
//否则第1个读线程获取锁次数减1
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {//只获取1次
readHolds.remove(); //直接移出threadLocal
if (count <= 0)
throw unmatchedUnlockException();
}
//否则将该线程threadLocal记录的获取锁次数减1
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;//计算出获取读锁线程个数
//cas设置获取读锁线程个数
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;
}
}
从上面可以看出,state这个变量,高16位记录了获取读锁线程个数,然后每个读线程通过ThreadLocal记录了每个线程重复获取锁次数;低16位记录写线程获取锁次数。
读锁:是一个共享锁,会阻塞尝图获取写锁的线程
写锁:本质是一个独占锁和ReentrantLock很像,会阻塞试图获取读锁的线程和其它尝图获取写锁的线程
总结
1.写锁的获取与释放
写锁获取:
写锁是一个支持重进入的排它锁。如果当前线程已经获取了写锁,则增加写状态。如果当 前线程在获取写锁时,读锁已经被获取(读状态不为0)或者该线程不是已经获取写锁的线程, 则当前线程进入等待状态。
写锁释放:
写锁的释放与ReentrantLock的释放过程基本类似,每次释放均减少写状态,当写状态为0 时表示写锁已被释放,从而等待的读写线程能够继续访问读写锁,同时前次写线程的修改对 后续读写线程可见。
2.读锁的获取与释放
读锁的获取:
读锁是一个支持重进入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问 (或者写状态为0)时,读锁总会被成功地获取,而所做的也只是(线程安全的)将持有读线程锁的个数+1,将本线程记录重入的次数threadLocal变量加1。如 果当前线程已经获取了读锁,则增加读状态。如果当前线程在获取读锁时,写锁已被其他线程 获取,则进入等待状态。
锁降级:
锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读 锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到 读锁,随后释放(先前拥有的)写锁的过程。
写锁降级成读锁
读锁阻塞写锁的,只有所有读锁释放后,写锁才获取,所有写锁可看到新写入的肉容
写锁不能降级
即有写线程、读线程同时运行
写锁写入内容,读线程可能看不到最新写入的线程
lockSupport工具
LockSupport定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功 能,而LockSupport也成为构建同步组件的基础工具。
LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread thread) 方法来唤醒一个被阻塞的线程