1. 读写锁简介
- 读写锁的内部包含两把锁:一把是读(操作)锁,是一种共享锁;另一把是写(操作)锁,是一种独占锁
- 在没有写锁的时候,读锁可以被多个线程同时持有
- 写锁被一个线程持有,其他的线程不能再持有写锁,抢占写锁会阻塞,抢占读锁也会阻塞
读写互斥原则:
- 读读相容
- 读写互斥
- 写写互斥
解决线程安全 问 题使用 ReentrantLock 就可以 ,但是 ReentrantLock 是独占锁 ,某时只有一个线程可以获取该锁,而实际中会有写少读多的场景,因此就需要读写锁ReentrantReadWriteLock
JUC包中的读写锁接口为ReadWriteLock:
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading
*/
Lock readLock();//返回读锁
/**
* Returns the lock used for writing.
*
* @return the lock used for writing
*/
Lock writeLock();//返回写锁
}
2. ReentrantReadWriteLock类图分析

1. ReentrantReadWriteLock实现了ReadWritrLock接口
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
2. FairSync、NonfairSync继承Sync类,提供了公平和非公平的实现
static final class NonfairSync extends Sync{
}
static final class FairSync extends Sync {
}
abstract static class Sync extends AbstractQueuedSynchronizer{
}
3. WriteLock、Sync、ReadLock、FairSync、NonfaruSyns都是ReadWritrLock的静态内部类
AQS 中只维护了 一个 state 状态,而 ReentrantReadWriteLock 则 需要维护读状态和写状态
public abstract class AbstractQueuedSynchronizer{
private volatile int state;//state是int类型 32位
}
用 state 的高16 位表示读状态,也就是获取到读锁的次数;使用低 16 位表示获取到写锁的线程的可重入次数 。
static final int SHARED_SHIFT = 16;
//共享锁(读锁)状态单位值 65536 1<<16 2^16
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
//共享锁线程最大个数 65535
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
//排它锁(写锁)掩码,二进制,15 个 1
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
//返回读锁线程数
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) {
return c >>> SHARED_SHIFT; }
//返回写锁可重入个数
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) {
return c & EXCLUSIVE_MASK; }
//firstReader 用来记录第一个获取到读锁的线程
private transient Thread firstReader = null;
//firstReaderHoldCount 则记录第 一个获取到读锁的线程获取读锁的可重入次数
private transient int firstReaderHoldCount;
//cachedHoldCounter 用来记录最后 一个获取读锁的线程获取读锁 的可重入次数
private transient HoldCounter cachedHoldCounter;
/*
readHolds是ThreadLocal变量 ,用来存放除去第一个获取读锁线程外的其他线程获取读锁的可重入次数 。ThreadLocaHoldCounter 继承了ThreadLocal ,因而重写的initialValue 方法返回 一个 HoldCounter 对象
*/
private transient ThreadLocalHoldCounter readHolds;
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
3. 写锁的获取和释放
ReentrantReadWriteLock 中 写锁使用 WriteLock 来实现
- 写锁是可重入锁
- 如 果当前没有线程获取到读锁和写锁, 则当前线程可以获取到写锁然后返回
- 如果当前己经有线程获取到读锁和写锁,则当前请求写锁的线程会被阻塞挂起
1. void lock()
public static class WriteLock implements Lock, java.io.Serializable {
public void lock() {
sync.acquire(1);
}
}
//AbstractQueuedSynchronizer类
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//Sync类
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
//c!=0说明读锁或者写锁已经被某线程获取
if (c != 0) {
//代码1处
// (Note: if c != 0 and w == 0 then shared count != 0)
// w=O说明已经有线程获取了读锁
// w!=O 并且当前线程不是写锁拥有者则返回false
if (w == 0 || current != getExclusiveOwnerThread())//代码2处
return false;
//说明当前线程获取了写锁,判断可重入次数
if (w + exclusiveCount(acquires) > MAX_COUNT)//代码3处
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);//设置可重入次数 代码4处
return true;
}
//第 一个写线程获取写锁
//代码5处
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
tryAcquire方法中:
代码1处:如果当前 AQS 状态值不为 0 则说明 当前己经有线程获取到了读锁或者写锁
代码2处:
- 如果 w==0 说明状态值 的低16 位为0(低16位表示写状态) ,而 AQS 状态值不为 0,则说明高16位(高16位表示读状态)不为 0 ,这暗示己经有线程获取了读锁 ,所以直接返回 false
- 如果 w! =0 则说明 当前已经有线程获取了该写锁,再看当前线程是不是该锁的持有者 ,如果不是则返回 false
代码3处

本文详细介绍了Java中的ReentrantReadWriteLock读写锁,包括其内部结构、读写锁的获取与释放过程,以及公平与非公平策略。通过实例展示了如何在多线程环境中使用读写锁提高并发性能,确保线程安全。
最低0.47元/天 解锁文章
341

被折叠的 条评论
为什么被折叠?



