ReentrantReadWriteLock(读写锁)
适用于读多写少的场景。(读不上锁,写上锁)
将原来的锁,分割为两把锁:读锁、写锁。适用于读多写少的场景,读锁可以并发,写锁与其他锁互斥。写写互斥、写读互斥、读读兼容。
Demo如下:
public class ThreadDemo {
static volatile int a;
public static void readA() {
System.out.println(a);
}
public static void writeA() {
a++;
}
public static void main(String[] args) {
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
Thread readThread1 = new Thread(() -> {
readLock.lock();
try {
readA();
} finally {
readLock.unlock();
}
});
Thread readThread2 = new Thread(() -> {
readLock.lock();
try {
readA();
} finally {
readLock.unlock();
}
});
Thread writeThread = new Thread(() -> {
writeLock.lock();
try {
writeA();
} finally {
writeLock.unlock();
}
});
readThread1.start();
readThread2.start();
writeThread.start();
}
}
核心变量和构造器
public interface ReadWriteLock {
// 用于获取读锁
Lock readLock();
// 用于获取写锁
Lock writeLock();
}
readerLock和writerLock变量用于支撑以上描述的ReadWriteLock接口的读锁和写锁方法。通过构造方法得知,读写锁对象的创建和用例均依赖于公平锁或者非公平锁同步器。
public class ReentrantReadWriteLock implements ReadWriteLock {
// 读锁对象
private final ReentrantReadWriteLock.ReadLock readerLock;
// 写锁对象
private final ReentrantReadWriteLock.WriteLock writerLock;
// 同步器
final Sync sync;
// 默认构造器,创建了非公平锁
public ReentrantReadWriteLock() {
this(false);
}
// 根据fair变量,来选择创建不同的锁:公平锁 FairSync 和非公平锁 NonfairSync
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
// 用同步器来创建读写锁对象
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
}
核心内部类
Sync类:
abstract static class Sync extends AbstractQueuedSynchronizer {
// 高16位用于表示读锁
static final int SHARED_SHIFT = 16;
// 用于对高16位操作:加1 减1
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 最大读锁量
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 用于获取低16位的值。例如 获取低八位:0000 0000 1111 1111
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** 获取当前持有读锁的线程数量 */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** 获取当前持有写锁的线程数量 */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
// 高16位为所有读锁获取,那么我想知道每个线程对于读锁重入的次数?采用ThreadLocal来进行统计,每个线程自己统计自己的
static final class HoldCounter {
int count = 0;
final long tid = getThreadId(Thread.currentThread());
}
// 继承自ThreadLocal,重写了其中的initialValue方法,该方法将在线程第一次获取该变量时调用初始化HoldCounter计数器
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
// 创建ThreadLocal对象
private transient ThreadLocalHoldCounter readHolds;
// 缓存最后一个线程获取的读锁数量
private transient HoldCounter cachedHoldCounter;
// 保存获取到该锁的第一个读锁线程
private transient Thread firstReader = null;
// 保存第一个该锁的第一个读锁线程获取到的读锁数量
private transient int firstReaderHoldCount;
Sync() {
// 构造器中初始化ThreadLocalHoldCounter ThreadLocal对象
readHolds = new ThreadLocalHoldCounter();
// 用于保证可见性,使用了state变量的volatile语义
setState(getState());
}
}
tryAcquire获取写锁的流程
用AQS调用,用于子类实现自己上锁的逻辑,和原有获取互斥锁保持一致
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取当前状态值和互斥锁的数量
int c = getState();
int w = exclusiveCount(c);
// 状态值有效
if (c != 0) {
// 有线程获取到了读锁或者当前线程不是持有互斥锁的线程
if (w == 0 || // 有线程获取到了读锁
current != getExclusiveOwnerThread()) // 有线程获取到了写锁
// 返回false 让AQS执行阻塞操作
return false;
// 写锁重入,而又由于写锁的数量保存在低16位,所以直接加就行了
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c + acquires);
return true;
}
// 既没有读锁,也没有写锁
if (writerShouldBlock() || // 由子类实现判断当前线程是否应该获取写锁
!compareAndSetState(c, c + acquires)) // 通过CAS抢写锁
return false;
// 获取写锁成功,那么将当前线程标识为获取互斥锁的线程对象
setExclusiveOwnerThread(current);
return true;
}
tryAquire获取读锁的流程
protected final int tryAcquireShared(int unused) {
// 获取到当前线程对象
Thread current = Thread.currentThread();
// 获取到当前状态值
int c = getState();
if (exclusiveCount(c) != 0 && // 有没有线程持有写锁
getExclusiveOwnerThread() != current) // 如果有线程获取到了互斥锁,那么进一步看看是不是当前线程
// 不是当前线程,那么直接返回-1,告诉AQS获取共享锁失败
return -1;
// 获取到读锁的持有数量
int r = sharedCount(c);
if (!readerShouldBlock() && // 让子类来判定当前获取读锁的线程是否应该被阻塞
r < MAX_COUNT && // 判断是否发生了溢出
compareAndSetState(c, c + SHARED_UNIT)) { // 直接CAS 增加state的高16位的读锁持有数量
// 增加高16位之前的计数为0,此时表明当前线程就是第一个获取读锁的线程
if (r == 0) {
// 注意:持有两个变量来优化threadlocal
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
// 当前获取读锁的线程就是一个线程,那么此时表明:锁重入,直接++计数位即可
firstReaderHoldCount++;
} else {
// 当前线程不是第一个读线程,此时将其获取读锁的次数保存在ThreadLocal中
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);
}