ReentrantReadWriteLock(可重入读写锁)是Java中用于并发控制的一个重要类,提供了读写锁的实现。读写锁允许多个线程同时读取共享资源,但在写入时需要独占访问,以确保线程安全性和性能优化。该锁机制分为读锁和写锁两种模式,允许多个线程同时获取读锁,但只允许一个线程获取写锁。
读写锁(ReentrantReadWriteLock)是一种并发控制机制,允许多个线程同时访问共享资源,但在对共享资源进行读写操作时采取不同的控制策略,以提高并发访问效率。
基本原理包括:
读锁(共享锁):允许多个线程同时获取读锁并且并行地访问共享资源。在读锁存在时,不会阻塞其他线程获取读锁,从而实现读取操作的并发性。读锁在没有写锁时可以被多个线程同时持有。
写锁(独占锁):只允许一个线程独占地获取写锁,此时其他线程无法获取读锁或写锁。写锁的获取和释放是独占性的,确保在写入操作时,不会有其他线程读取或写入共享资源。
可重入性:允许同一个线程重复获取同一种类型的锁,即允许读线程获取多次读锁,也允许写线程在持有写锁时再次获取写锁。这种机制防止了自己持有的锁成为阻塞自己的因素,避免了死锁的发生。
读写锁的基本原理是允许多个线程在不同的时间点以不同的方式(读或写)并发地访问共享资源。通过合理地管理读锁和写锁的获取,可以提高对共享资源的并发访问效率,降低资源争用带来的性能开销。选择合适的锁策略对于提高并发性能和确保数据一致性非常重要。
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class SharedResource {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private int sharedData = 0;
public int readData() {
lock.readLock().lock(); // 获取读锁
try {
// 读取共享资源的数据
return sharedData;
} finally {
lock.readLock().unlock(); // 释放读锁
}
}
public void writeData(int newData) {
lock.writeLock().lock(); // 获取写锁
try {
// 更新共享资源的数据
sharedData = newData;
} finally {
lock.writeLock().unlock(); // 释放写锁
}
}
}
在示例中,SharedResource
类维护一个共享的数据 sharedData
。readData()
方法获取读锁来读取共享数据,而 writeData()
方法获取写锁来更新共享数据。
在实际应用中,读写锁能够提高并发读取操作的效率。当读操作远多于写操作时,多个线程可以并发地持有读锁,提高了系统的吞吐量。但是,当有线程持有写锁时,其他线程无法获取读锁或写锁,确保了写操作的独占性和数据的一致性。
在使用读写锁时,需要注意避免死锁和避免长时间持有锁而影响系统性能。
ReentrantReadWriteLock
是Java中用于实现读写锁的类,它允许多个线程同时读取共享资源,但是只允许一个线程写入共享资源。在此针对JDK1.8进行ReentrantReadWriteLock的源码分析,下面是该类的主要成员变量和方法
Sync
:ReentrantReadWriteLock
的内部同步器,包含了对读锁和写锁的管理逻辑,是一个抽象类,有两个子类 NonfairSync
和 FairSync
。readLock
:ReentrantReadWriteLock
中的读锁对象,用于读取操作的加锁。writeLock
:ReentrantReadWriteLock
中的写锁对象,用于写入操作的加锁。ReentrantReadWriteLock()
:无参构造函数,创建一个非公平的读写锁。ReentrantReadWriteLock(boolean fair)
:带参数的构造函数,可以创建非公平或公平的读写锁。readLock()
:返回用于读取操作的读锁对象。writeLock()
:返回用于写入操作的写锁对象。Sync
:ReentrantReadWriteLock
的内部静态类,用于实现读写锁的同步逻辑。包含了读锁和写锁的获取和释放的控制方法。readLock()
和 writeLock()
返回的锁对象方法)lock()
:获取锁。tryLock()
:尝试获取锁,如果获取成功返回 true
,否则返回 false
。unlock()
:释放锁。getReadHoldCount()
:返回当前线程保持读锁的次数。getWriteHoldCount()
:返回当前线程保持写锁的次数。getQueueLength()
:返回正在等待获取读锁或写锁的线程数。isFair()
:判断锁是否是公平的。ReentrantReadWriteLock
基于内部的Sync
同步器类实现了读写锁的控制逻辑,对并发读和独占写提供了良好的支持,使得在某些场景下能够高效地实现对共享资源的读写操作。
源码部分
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
/**
* Creates a new {@code ReentrantReadWriteLock} with
* default (nonfair) ordering properties.
*/
public ReentrantReadWriteLock() {
this(false);
}
/**
* Creates a new {@code ReentrantReadWriteLock} with
* the given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
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
同步器类是ReentrantReadWriteLock
的内部静态类,用于实现读写锁的同步逻辑。它有两个子类,NonfairSync
和FairSync
,分别对应非公平和公平的锁获取机制。
读写锁的实现原理基于共享模式和独占模式的区分,使用了Sync
同步器类来管理锁的获取和释放。它内部维护了读锁和写锁的状态,以及相应的线程等待队列。以下是大致的实现原理:
读锁获取和释放:
写锁获取和释放:
Fair 和 Nonfair 两种模式:
Sync
同步器类内部使用了类似AQS(AbstractQueuedSynchronizer)的队列(等待队列),用于管理线程获取锁的竞争关系。它通过状态的更新和线程的阻塞与唤醒,实现了读写锁的精确控制,保证了并发读和独占写的正确性和性能。
读写锁在应用中可以提高并发读取操作的效率,特别是读远远多于写的情况下。通过合理的使用读写锁,可以提升系统的并发处理能力和性能。
Sync 源码
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 6317671515068378041L;
/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 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; }
/**
* A counter for per-thread read hold counts.
* Maintained as a ThreadLocal; cached in cachedHoldCounter
*/
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
/**
* ThreadLocal subclass. Easiest to explicitly define for sake
* of deserialization mechanics.
*/
static final class ThreadLocalHoldCounter
extends ThreadLocal {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
/**
* The number of reentrant read locks held by current thread.
* Initialized only in constructor and readObject.
* Removed whenever a thread's read hold count drops to 0.
*/
private transient ThreadLocalHoldCounter readHolds;
/**
* The hold count of the last thread to successfully acquire
* readLock. This saves ThreadLocal lookup in the common case
* where the next thread to release is the last one to
* acquire. This is non-volatile since it is just used
* as a heuristic, and would be great for threads to cache.
*
* Can outlive the Thread for which it is caching the read
* hold count, but avoids garbage retention by not retaining a
* reference to the Thread.
*
*
Accessed via a benign data race; relies on the memory
* model's final field and out-of-thin-air guarantees.
*/
private transient HoldCounter cachedHoldCounter;
/**
* firstReader is the first thread to have acquired the read lock.
* firstReaderHoldCount is firstReader's hold count.
*
*
More precisely, firstReader is the unique thread that last
* changed the shared count from 0 to 1, and has not released the
* read lock since then; null if there is no such thread.
*
*
Cannot cause garbage retention unless the thread terminated
* without relinquishing its read locks, since tryReleaseShared
* sets it to null.
*
*
Accessed via a benign data race; relies on the memory
* model's out-of-thin-air guarantees for references.
*
*
This allows tracking of read holds for uncontended read
* locks to be very cheap.
*/
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState()); // ensures visibility of readHolds
}
...
}
// 默认是非公平锁
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
// 获取锁逻辑
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 尝试获取锁
protected final boolean tryAcquire(int acquires) {
// 当前线程
Thread current = Thread.currentThread();
// 获取锁状态 存在读锁或者写锁,状态就不为0
int c = getState();
// 获取写锁的重入数
int w = exclusiveCount(c);
// 当前stat状态!=0 表示已经有其他线程获取了读锁或者写锁
if (c != 0) {
// c != 0 && w == 0表示存在读锁
// 当前存在读锁或写锁已被其他写线程获取,则获取写锁失败
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;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// cas成功,将锁的持有者设置为当前线程
setExclusiveOwnerThread(current);
return true;
}
// 从队列中唤醒线程
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取头节点
final Node p = node.predecessor();
// 如果节点为头节点,再次尝试获取锁
if (p == head && tryAcquire(arg)) {
// 获取锁成功 出队操作
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 获取锁失败,调用LockSupport.park(this)阻塞线程,并清除中断标记
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// 入队
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
// 初始化头节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
// 设置尾节点
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
// 释放锁逻辑
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 获取锁状态,将锁重入次数-releases
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
// 当state为0,表示真正的释放锁
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
// 获取共享锁
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 如果写锁已经被获取并且获写取锁的线程不是当前线程,当前线程获取读锁失败返回-1
// 判断锁降级
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 计算读锁的数量
int r = sharedCount(c);
// 读锁是否阻塞readerShouldBlock()公平与非公平的实现
if (!readerShouldBlock() &&
r < MAX_COUNT && // 持有锁的线程小于最大数65535
compareAndSetState(c, c + SHARED_UNIT)) {// cas设置获取读锁线程的数量
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);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// 释放读锁
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// 如果当前线程是第一个获取读锁的线程
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
// 锁的持有数-1
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;// 重入次数-1
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
// cas更新同步状态,如果锁状态为0,表示释放锁
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
ReentrantReadWriteLock
是Java中用于实现读写锁的一种机制。它的优缺点、适用范围、性能特性以及最佳实践如下:
ReentrantReadWriteLock
适用于读多写少、对读取性能要求较高的场景,但在高并发写入的情况下可能存在写锁饥饿问题,需要谨慎使用。在实际使用中需要根据业务特点和需求综合考虑,选择最适合的锁机制。