在《JUC之ReentrantLock》与《JUC之CountDownLatch》中分别介绍了ReentrantLock与CountDownLatch。其中ReentrantLock的实现依赖于AbstractQueuedSynchronizer独占模式,CountDownLatch的实现依赖于AbstractQueuedSynchronizer共享模式。那有没有一个类的实现不仅依赖了独占模式而且还有共享模式呢?ReentrantReadWriteLock就是这样的类,ReentrantReadWriteLock的实现既依赖了AbstractQueuedSynchronizer独占模式,而且还依赖了AbstractQueuedSynchronizer共享模式。
ReentrantReadWriteLock叫做可重入读写锁。在ReentrantReadWriteLock内部提供了两个内部类,一个是WriteLock、另一个是ReadLock。WriteLock是写锁,其实现依赖于AbstractQueuedSynchronizer独占模式,因此使用WriteLock,每次只有一个线程能够获取到并且持有锁。ReadLock是读锁,其实现依赖于AbstractQueuedSynchronizer共享模式,因此使用ReadLock,每次可以有多个线程获取到并且持有锁。
读锁即共享锁,或者说是相互不阻塞的,多个线程在同一时刻可以同时读取同一个资源,而互不干扰。写锁即排他锁,一个线程的写锁会阻塞其他线程的写锁和读锁。(引用改编自《高性能MySQL》)
█ ReentrantReadWriteLock
在ReentrantReadWriteLock持有这样的三个字段:
// 读锁
private final ReentrantReadWriteLock.ReadLock readerLock;
// 写锁
private final ReentrantReadWriteLock.WriteLock writerLock;
// 同步器
final Sync sync;
ReentrantReadWriteLock的构造器:
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
从构造器可以看出,ReentrantReadWriteLock也是分公平锁和非公平锁的。还有在ReadLock中与WriteLock中持有的是同一个ReentrantReadWriteLock对象,因此两者也会共享同一个Sync同步器对象。(记住这一点,方便后面源码的理解)
下面分别来看看ReentrantReadWriteLock中的Sync、WriteLock、ReadLock。
█ Sync
与ReentrantLock一样,ReentrantReadWriteLock在内部也是持有一个Sync类型的字段用来表示同步器类型。
final Sync sync;
ReentrantReadWriteLock的同步器类型也分为公平、非公平两种。
- Sync
还是同样的套路,内部类Sync继承AbstractQueuedSynchronizer,重写tryAcquire、tryRelease方法来实现自定义获取独占锁、释放独占锁的逻辑(写锁WriteLock使用)。重写tryAcquireShared、tryReleaseShared方法来实现自定义获取共享锁、释放共享锁的逻辑(读锁ReadLock使用)。
(0)sharedCount、exclusiveCount
前面说过在ReadLock与WriteLock中持有的是同一个ReentrantReadWriteLock对象,因此也就持有同一个Sync对象,也就共享state值了。因为设置是可重入锁与共享锁,所以state的值用来表示锁被线程持有的次数,即state的值包含了读锁与写锁两者共同被持有的次数。那是不是需要单独记录下读锁和写锁的持有次数呢?sharedCount方法用来返回读锁持有次数,exclusiveCount方法用来返回写锁持有次数。
// 常量16
static final int SHARED_SHIFT = 16;
// 读锁共享单元,1 << SHARED_SHIFT为2的16次方==65536
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 可重入锁的最大重入次数,为65535
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// c >>> SHARED_SHIFT的效果相当于c整除2的16次方,即c/65536
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// c & EXCLUSIVE_MASK的效果相当于c与65535取余,即c%65535
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
(1)tryAcquire
获取写锁。
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取state值,即锁的状态
int c = getState();
// 写锁的被持有次数(可重入嘛)
int w = exclusiveCount(c);
// c=state不为0,表示有线程持有锁了
if (c != 0) {
// w=0,表示写锁持有次数为0,即还可以线程持有写锁,但state不为0又表示
// 锁被持有了,那只能是读锁被持有了。
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 写锁的重入次数不为超过65535次
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 变更state的值,增加锁持有次数
setState(c + acquires);
return true;
}
// writerShouldBlock,写锁需要阻塞
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 设置写锁独占线程为当前线程。
setExclusiveOwnerThread(current);
return true;
}
上面代码有几点需要特别说明:
①c!=0,c的值取自state值,表示了锁的持有次数。当c!=0时,表示此时有线程持有了锁,但不知道是写锁还是读锁。w的值表示写锁的持有次数,当w==0时成立,即写锁的持有次数为0,也就是没有线程占有写锁。而锁的数量又不为0,那只能是读锁被持有了。w==0,方法直接返回false获取锁失败,也就可以看出,ReentrantReadWriteLock的规则之一:当读锁被占用时,写锁是无法被获取到的,即所有的线程都是无法获取到写锁的,此时线程只能暂时进入等待队列。
②w!=0,即写锁被持有了,current != getExclusiveOwnerThread()用于判断正在持有写锁的线程是不是当前线程,因为写锁是独占锁嘛,同时只能有一个线程获取到写锁。接下来该if语句块的内容就用于写锁的重入了。
③c==0,即当时无论是读锁,还是写锁都没有被持有,则可以尝试去获取写锁了。writerShouldBlock方法是标记尝试去获取写锁的线程是否阻塞,返回true则表示需要阻塞,返回false不用阻塞,直接去使用CAS操作获取写锁。如果获取成功,将写锁的正在持有的线程标记为当前线程。
④writerShouldBlock:
抽象方法,由具体的子类实现,即由FairSync、NonfairSync
abstract boolean writerShouldBlock();
分别查看FairSync、NonfairSync中的实现逻辑。
FairSync:
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
NonfairSync:
final boolean writerShouldBlock() {
return false; // writers can always barge
}
writerShouldBlock原来用来实现公平锁和非公平锁的功能。公平锁获取的时候,需要查看在等待队列中,其前面是否已有等待的线程了。非公平锁也不需要考虑,直接去尝试获取锁。
(2)tryRelease
释放写锁。
protected final boolean tryRelease(int releases) {
// 必须是正在持有写锁的线程才能释放,否则抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// state值减1,释放一次锁
int nextc = getState() - releases;
// 写锁的持有数量是否为0,等于0表示写锁被释放完了。
boolean free = exclusiveCount(nextc) == 0;
if (free) // 如果写锁被释放完了,则没有线程占用写锁了,擦除占有的标记
setExclusiveOwnerThread(null);
// 更新state值
setState(nextc);
return free;
}
(3)tryAcquireShared
获取读锁。
protected final int tryAcquireShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取同步状态state值
int c = getState();
// 写锁被持有了且持有写锁的线程不是当前线程,则当前线程无法获取到读锁
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 获取读锁持有次数
int r = sharedCount(c);
// readerShouldBlock也是起到了控制公平与非公平读锁的作用
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 != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
①c的值取自state的值,表示锁被持有的次数。exclusiveCount(c)返回写锁被持有的次数,exclusiveCount(c)!=0表示写锁已经被持有,此时如果尝试获取读锁的线程不是正在持有写锁的线程,其获取读锁失败。ReentrantReadWriteLock的规则之二:当写锁被占用时,只有正在持有该写锁的线程才有机会获取到读锁,非持有写锁的线程无法获取到读锁。
②readerShouldBlock
readerShouldBlock是Sync中提供的抽象方法,具体内容交给子类去实现。
abstract boolean readerShouldBlock();
FairSync:
在等待队列中前面已经有等待的线程了就返回true。
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
NonfairSync:
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
apparentlyFirstQueuedIsExclusive查看在等待队列中,第一个线程节点是否为独占模式,即在等待获取写锁。
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
③第13行开始的if语句块,满足条件进入语句块内表示获取读锁成功,接下来做记录就行了。r==0表示该读锁是第一次被获取到,则将firstReader的引用指向当前线程,标记当前线程是第一个读线程。firstReaderHoldCount用于记录第一个读线程其持有的读锁的次数。firstReader == current用于读锁的重入操作,如果满足则表示第一个读线程在重复获取读锁,将firstReaderHoldCount持有读锁的次数增加1。如果不满足上面两个条件,则表示其他线程(非第一个读线程的线程)在获取读锁,使用ThreadLocalHoldCounter记录该线程持有锁的次数。
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
readHolds的类型ThreadLocalHoldCounter,ThreadLocalHoldCounter借助了ThreadLocal的功能:
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
④fullTryAcquireShared
fullTryAcquireShared方法前面的逻辑是快速获取读锁的逻辑,如果无法快速获取读锁,则进入fullTryAcquireShared方法去尽力尝试获取读锁。
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
// 仍然是如果写锁被持有了,只有持有写锁的线程才能有机会获取读锁
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
} else if (readerShouldBlock()) {// 获取读锁时需要阻塞
if (firstReader == current) {
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
// 读锁的重入次数不能超过65535次
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// CAS操作更改state值来获取读锁,如果获取成功记录HoldCount持有读锁次数等信息
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
(4)tryReleaseShared
释放读锁。如果当前线程是第一个读线程,擦除firstReader的引用,减少firstReaderHoldCount的次数一次。否则当前线程不是第一个读线程,减少其持有的线程次数一次。
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 != 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;
}
} throw new Error("Maximum lock count exceeded");
// CAS操作更改state值来获取读锁,如果获取成功记录HoldCount持有读锁次数等信息
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
- NonfairSync
以非公平的方式去获取锁(读锁和写锁)。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
}
- FairSync
以公平的方式去获取锁(读锁和写锁)。
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
█ WriteLock(写锁)
WriteLock写锁,其持有一个Sync对象,主要功能都交给了Sync处理。
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
- lock
调用Sync的acquire方法获取独占锁。
public void lock() {
sync.acquire(1);
}
- unlock
调用Sync的release方法释放独占锁。
public void unlock() {
sync.release(1);
}
- tryLock
public boolean tryLock( ) {
return sync.tryWriteLock();
}
即Sync的tryWriteLock方法,其和tryAcquire的功能和逻辑大致相同,唯一的区别是在tryWriteLock方法中缺少writerShouldBlock条件的判断:
final boolean tryWriteLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c != 0) {
int w = exclusiveCount(c);
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
if (!compareAndSetState(c, c + 1))
return false;
setExclusiveOwnerThread(current);
return true;
}
█ ReadLock(读锁)
和WriteLock一样,ReadLock读锁其也持有一个Sync对象,主要功能都交给了Sync处理。对于同一个ReentrantReadWriteLock,WriteLock与ReadLock持有的是同一个Sync对象。
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
- lock
调用Sync的acquireShared方法获取共享锁。
public void lock() {
sync.acquireShared(1);
}
- unlock
调用Sync的releaseShared方法释放共享锁。
public void unlock() {
sync.releaseShared(1);
}
- tryLock
public boolean tryLock( ) {
return sync.tryReadLock();
}
即Sync的tryReadLock方法,其和tryAcquireShared的功能和逻辑大致相同,唯一的区别是在tryReadLock方法中缺少readerShouldBlock条件的判断:
final boolean tryReadLock() {
Thread current = Thread.currentThread();
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return false;
int r = sharedCount(c);
if (r == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (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 != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return true;
}
}
}
█ 使用
准备代码环境:
public class Main5 {
public static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
// 读锁
public static final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
// 写锁
public static final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
public static void main(String[] args) {
// 创建3个读线程
Thread readThread1 = new Thread(()->read(), "readThread1");
Thread readThread2 = new Thread(()->read(), "readThread2");
Thread readThread3 = new Thread(()->read(), "readThread3");
// 创建3个写线程
Thread writeThread1 = new Thread(()->write(), "writeThread1");
Thread writeThread2 = new Thread(()->write(), "writeThread2");
Thread writeThread3 = new Thread(()->write(), "writeThread3");
}
public static void startThread(Thread thread) {
thread.start();
}
/**
* 读方法
*/
public static void read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName()+" read................"
+System.currentTimeMillis());
// 使线程睡5秒,方便观察锁的效果
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
}
/**
* 写方法
*/
public static void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName()+" write................"
+System.currentTimeMillis());
// 使线程睡5秒,方便观察锁的效果
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
}
通过观察运行结果输出的时间戳来观察锁的获取情况。
(1)让3个读线程同时去获取读锁
public static void main(String[] args) {
// 创建3个读线程
Thread readThread1 = new Thread(()->read(), "readThread1");
Thread readThread2 = new Thread(()->read(), "readThread2");
Thread readThread3 = new Thread(()->read(), "readThread3");
startThread(readThread1);
startThread(readThread2);
startThread(readThread3);
}
同时运行3个读线程,即模拟多个线程同时获取读锁,从下图的运行结果可以看出,对于读锁,多个线程是可以同时获取到的。(打印的时间戳相同或者相差特别特别小表明三个线程几乎同时运行的)
(2)让3个写线程同时去获取写锁
public static void main(String[] args) {
// 创建3个写线程
Thread writeThread1 = new Thread(()->write());
Thread writeThread2 = new Thread(()->write());
Thread writeThread3 = new Thread(()->write());
startThread(writeThread1);
startThread(writeThread2);
startThread(writeThread3);
}
同时运行3个写线程,即模拟多个线程同时获取写锁。从运行结果可以看出,对于写锁,每次只能有一个线程获取到。只有等持有写锁的线程释放了写锁,其他线程才能继续获取到写锁。(线程运行时间差别了大约5000毫秒,表明线程是一个一个等待运行的)
(3)让读锁先被持有,再去获取写锁
①获取读锁和写锁的不是同一个线程:
public static void main(String[] args) {
// 读线程
Thread readThread1 = new Thread(()->read(), "readThread1");
// 写线程
Thread writeThread1 = new Thread(()->write(), "writeThread1");
startThread(readThread1);
startThread(writeThread1);
}
先运行一个读线程,再运行一个写线程,模拟当读锁被占用时,其他线程去获取写锁。从运行结果可以看出,当读锁被占用时,写锁是无法被获取的,需要等读锁被释放了才能获取(打印的时间戳相差了大约5000毫秒)。
②获取读锁和写锁的是同一个线程:
public static void main(String[] args) {
Thread rwThread = new Thread(()->{
readLock.lock();
System.out.println("rwThread readLock lock...");
writeLock.lock();
System.out.println("rwThread writeLock lock...");
writeLock.unlock();
readLock.unlock();
}, "rwThread");
startThread(rwThread);
}
从运行结果可以看出,当一个线程获取到读锁之后,在没有释放读锁的时候想再去获取写锁的话,获取写锁会不成功,一直阻塞等待。所以不管是同一个线程还是不同的线程,只要读锁被占用了,写锁是都没法被获取到的。(这一点在Sync的tryAcquire方法中有体现)
(4)让写锁先被持有,再去获取读锁
①获取写锁和读锁的不是同一个线程:
public static void main(String[] args) {
// 读线程
Thread readThread1 = new Thread(()->read(), "readThread1");
// 写线程
Thread writeThread1 = new Thread(()->write(), "writeThread1");
startThread(writeThread1);
startThread(readThread1);
}
从运行结果可以看出,时间戳相差了大约5000毫秒。因此当写锁被占用时,其他线程是无法获取到读锁的,只有当写锁被释放了才能去获取读锁。
②获取写锁和读锁的是同一个线程:
public static void main(String[] args) {
Thread rwThread = new Thread(()->{
writeLock.lock();
System.out.println("rwThread writeLock lock..."+System.currentTimeMillis());
readLock.lock();
System.out.println("rwThread readLock lock..."+System.currentTimeMillis());
readLock.unlock();
writeLock.unlock();
}, "rwThread");
startThread(rwThread);
}
从运行结果可以看出,如果写锁被占用了,占用写锁的那个线程是可以获取到读锁的,无需等待写锁被释放。
从上述四个程序验证可以看出:
①当读锁被占用时,线程是只能获取读锁的,无法获取写锁(读时,只能读,不能写)。
②当写锁被占用时,占用写锁的线程可以获取到读锁,其他线程既不能获取到写锁,也不能获取到读锁(可写线程可读,其他线程不可写不可读)。
当一个线程正在读时,其他线程也可以读,所有线程都不能写。
当一个线程正在写时,只有这个线程才可以读,其他线程既不能读,也不能写。