为了有了ReentrantLock还需要ReentrantReadWriteLock
ReentrantReadWriteLock是一个读写锁,它允许多个读操作同时进行,但在写操作时会阻止其他所有读和写操作。这种锁特别适合于读取远多于写入的场景,因为它可以提高并发性而不会牺牲数据一致性。
- 分离的读锁和写锁:ReentrantReadWriteLock提供了两个锁,一个用于读操作(readLock),另一个用于写操作(writeLock)。
- 读锁共享性:读锁可以被多个线程同时持有,只要没有线程持有写锁。
- 写锁独占性:写锁是独占的,只能由一个线程持有,并且在任何线程持有写锁时,读锁也不能被其他线程持有。
- 公平性选择:与ReentrantLock一样,ReentrantReadWriteLock也可以设置为公平模式。
尽管ReentrantLock提供了强大的功能,但它不支持区分读写操作的锁定策略。在某些情况下,使用ReentrantReadWriteLock可能更高效,因为它允许多个线程同时读取共享资源,从而减少了锁竞争的可能性。这对于读多写少的数据结构特别有用,比如缓存、计数器、集合等。
源码分析
ReentrantReadWriteLock实现了ReadWriteLock接口,ReadWriteLock接口定义了获取读锁和写锁的规范,具体需要实现类去实现;同时其还实现了Serializable接口,表示可以进行序列化,在源代码中可以看到ReentrantReadWriteLock实现了自己的序列化逻辑。Sync继承自AQS、NonfairSync继承自Sync类、FairSync继承自Sync类;ReadLock实现了Lock接口、WriteLock也实现了Lock接口。
内部类分析
- ReadLock: 实现了读锁的功能,提供了读取时的锁定和释放操作。当线程请求读锁时,如果当前没有写锁被其他线程持有,则允许获取读锁。
- WriteLock: 实现了写锁的功能,提供了写入时的排他锁定和释放操作。当线程请求写锁时,会阻塞直到所有读锁被释放并且没有其他写锁被持有。
- FairSync和NonfairSync: 都是 Sync 的子类,FairSync实现了公平的锁获取策略。在公平模式下,等待时间最长的线程将获得锁。NonfairSync实现了非公平的锁获取策略。在非公平模式下,不保证按照请求锁的顺序授予锁,新来的线程可能会插队获取锁。
- RWLock: 定义了读写锁的基本操作。
- SyncHelper: 用于帮助管理同步状态。
- HoldCounter: HoldCounter主要与读锁配套使用。HoldCounter主要有两个属性,count和tid,其中count表示某个读线程重入的次数,tid表示该线程的tid字段的值,该字段可以用来唯一标识一个线程
主要方法
- tryRelease 该方法用于释放写锁资源,首先会判断该线程是否为独占线程,若不为独占线程,则抛出异常,否则,计算释放资源后的写锁的数量,若为0,表示成功释放,资源不将被占用,否则,表示资源还被占用。
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;
}
- tryAcquire 用于实现锁的获取逻辑,允许线程以可重入的方式获取锁。其中,变量c为锁状态,变量w表示当前状态c中独占锁的持有数量。如果当前状态不为0,说明锁已经被持有。如果当前锁是共享的(w == 0),或者持有锁的线程不是当前线程,则直接返回false,表示获取失败。前状态不为0,说明锁已经被持有。如果当前线程已经持有锁(判断了重入次数是否超过了最大限制),则简单增加状态计数并返回true,表示获取成功:
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;
}
- tryReleaseShared 这个方法的作用是尝试释放共享锁(读锁)。
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// 当前线程为第一个读线程
if (firstReader == current) {
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
//获取缓存的HoldCounter对象rh,如果rh为空或者rh.tid不等于当前线程的ID,则从readHolds中获取新的HoldCounter对象。
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
//从readHolds中移除当前的HoldCounter对象。
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;
}
}
- tryAcquireShared 用于读锁线程获取读锁。如果exclusiveCount©不等于0且getExclusiveOwnerThread()不等于当前线程,说明有其他线程持有写锁,此时无法获取共享锁,直接返回-1。如果readerShouldBlock()为false且r(读取次数)小于MAX_COUNT,尝试通过CAS操作更新状态值c,将共享锁计数器加1。如果更新成功,根据不同情况更新firstReader和firstReaderHoldCount的值,并返回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 != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
代码使用示例
ReadThread和WriteThread是自定义的线程类,它们分别代表读线程和写线程。读线程:
public class ReadThread extends Thread{
private ReentrantReadWriteLock rrwLock;
public ReadThread(String name, ReentrantReadWriteLock rrwLock) {
super(name);
this.rrwLock = rrwLock;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " trying to lock");
try {
rrwLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " lock successfully");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rrwLock.readLock().unlock();
System.out.println(Thread.currentThread().getName() + " unlock successfully");
}
}
}
写线程:
public class WriteThread extends Thread{
private ReentrantReadWriteLock rrwLock;
public WriteThread(String name, ReentrantReadWriteLock rrwLock) {
super(name);
this.rrwLock = rrwLock;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " trying to lock");
try {
rrwLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " lock successfully");
} finally {
rrwLock.writeLock().unlock();
System.out.println(Thread.currentThread().getName() + " unlock successfully");
}
}
}
使用,创建两个读线程和一个写线程。然后启动这些线程:
public static void main(String[] args) {
ReentrantReadWriteLock rrwLock = new ReentrantReadWriteLock();
ReadThread rt1 = new ReadThread("rt1", rrwLock);
ReadThread rt2 = new ReadThread("rt2", rrwLock);
WriteThread wt1 = new WriteThread("wt1", rrwLock);
rt1.start();
rt2.start();
wt1.start();
}
执行结果: