ReentrantReadWriteLock源码分析

介绍

ReentrantReadWriteLock(可重入读写锁)是Java中用于并发控制的一个重要类,提供了读写锁的实现。读写锁允许多个线程同时读取共享资源,但在写入时需要独占访问,以确保线程安全性和性能优化。该锁机制分为读锁和写锁两种模式,允许多个线程同时获取读锁,但只允许一个线程获取写锁。

基本原理

        读写锁(ReentrantReadWriteLock)是一种并发控制机制,允许多个线程同时访问共享资源,但在对共享资源进行读写操作时采取不同的控制策略,以提高并发访问效率。

基本原理包括:

  1. 读锁(共享锁):允许多个线程同时获取读锁并且并行地访问共享资源。在读锁存在时,不会阻塞其他线程获取读锁,从而实现读取操作的并发性。读锁在没有写锁时可以被多个线程同时持有。

  2. 写锁(独占锁):只允许一个线程独占地获取写锁,此时其他线程无法获取读锁或写锁。写锁的获取和释放是独占性的,确保在写入操作时,不会有其他线程读取或写入共享资源。

  3. 可重入性:允许同一个线程重复获取同一种类型的锁,即允许读线程获取多次读锁,也允许写线程在持有写锁时再次获取写锁。这种机制防止了自己持有的锁成为阻塞自己的因素,避免了死锁的发生。

        读写锁的基本原理是允许多个线程在不同的时间点以不同的方式(读或写)并发地访问共享资源。通过合理地管理读锁和写锁的获取,可以提高对共享资源的并发访问效率,降低资源争用带来的性能开销。选择合适的锁策略对于提高并发性能和确保数据一致性非常重要。

示例用法

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 类维护一个共享的数据 sharedDatareadData() 方法获取读锁来读取共享数据,而 writeData() 方法获取写锁来更新共享数据。

        在实际应用中,读写锁能够提高并发读取操作的效率。当读操作远多于写操作时,多个线程可以并发地持有读锁,提高了系统的吞吐量。但是,当有线程持有写锁时,其他线程无法获取读锁或写锁,确保了写操作的独占性和数据的一致性。

        在使用读写锁时,需要注意避免死锁和避免长时间持有锁而影响系统性能。

源码分析

  ReentrantReadWriteLock是Java中用于实现读写锁的类,它允许多个线程同时读取共享资源,但是只允许一个线程写入共享资源。在此针对JDK1.8进行ReentrantReadWriteLock的源码分析,下面是该类的主要成员变量和方法

主要成员变量

  1. SyncReentrantReadWriteLock的内部同步器,包含了对读锁和写锁的管理逻辑,是一个抽象类,有两个子类 NonfairSyncFairSync
  2. readLockReentrantReadWriteLock中的读锁对象,用于读取操作的加锁。
  3. writeLockReentrantReadWriteLock中的写锁对象,用于写入操作的加锁。

主要方法

  1. ReentrantReadWriteLock():无参构造函数,创建一个非公平的读写锁。
  2. ReentrantReadWriteLock(boolean fair):带参数的构造函数,可以创建非公平或公平的读写锁。
  3. readLock():返回用于读取操作的读锁对象。
  4. writeLock():返回用于写入操作的写锁对象。

主要内部类

  1. SyncReentrantReadWriteLock的内部静态类,用于实现读写锁的同步逻辑。包含了读锁和写锁的获取和释放的控制方法。

主要方法(readLock()writeLock() 返回的锁对象方法)

  1. lock():获取锁。
  2. tryLock():尝试获取锁,如果获取成功返回 true,否则返回 false
  3. unlock():释放锁。

附加方法

  1. getReadHoldCount():返回当前线程保持读锁的次数。
  2. getWriteHoldCount():返回当前线程保持写锁的次数。
  3. getQueueLength():返回正在等待获取读锁或写锁的线程数。
  4. 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同步器

  Sync同步器类是ReentrantReadWriteLock的内部静态类,用于实现读写锁的同步逻辑。它有两个子类,NonfairSyncFairSync,分别对应非公平和公平的锁获取机制。

读写锁的实现原理基于共享模式和独占模式的区分,使用了Sync同步器类来管理锁的获取和释放。它内部维护了读锁和写锁的状态,以及相应的线程等待队列。以下是大致的实现原理:

  1. 读锁获取和释放

    • 当没有线程持有写锁且没有线程正在请求写锁时,其他线程可以并发地获取读锁。
    • 每个成功获取读锁的线程会增加读锁的计数,允许多个线程同时持有读锁。
    • 在释放读锁时,读锁的计数会减少,当计数为0时,表示没有线程持有读锁。
  2. 写锁获取和释放

    • 写锁是独占锁,一旦有线程持有写锁,其他线程无法获取读锁或写锁,保证了数据的独占性。
    • 当有线程持有读锁或写锁时,新的写锁请求会被阻塞,直到没有线程持有读锁或写锁。
    • 写锁在被释放后,其他线程可以继续获取读锁或写锁。
  3. Fair 和 Nonfair 两种模式

    • 公平模式(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.WriteLock

// 默认是非公平锁
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;
}

ReentrantReadWriteLock.ReadLock

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中用于实现读写锁的一种机制。它的优缺点、适用范围、性能特性以及最佳实践如下:

优点

  1. 读写分离:允许多个线程同时读取共享资源,提高了并发读取的效率。
  2. 可重入性:支持读锁和写锁的可重入,同一个线程可以多次获得同一把锁,避免了死锁情况。
  3. 公平性控制:可以根据需要创建公平锁或非公平锁,允许使用者根据实际场景进行选择。

缺点

  1. 写锁饥饿:如果写操作的频率高于读操作,可能导致写锁被一直等待,而读锁可以一直获取。
  2. 不支持锁降级:不支持将写锁降级为读锁,也不支持锁升级。

适用范围

  1. 读多写少场景:适用于读操作远远多于写操作的场景,提高了读取的并发性能。
  2. 可重入性要求高:对于需要在写锁保持期间也可以获得读锁的场景,支持可重入性的需求。

性能特性

  1. 读取性能高:读取操作在并发场景下性能优越,多个线程可以同时获取读锁。
  2. 写入性能受限:写入操作需要排他性,当写操作频繁时,可能会影响整体性能。

建议和最佳实践

  1. 合理选择公平性:根据实际需求选择公平锁或非公平锁,避免频繁的锁竞争带来的性能损耗。
  2. 适当控制锁的粒度:精心设计锁的粒度,避免过大或过小的锁粒度带来的性能问题。
  3. 谨慎处理锁升级问题:不支持锁升级,需要谨慎处理读锁和写锁的获取释放顺序。
  4. 避免写锁饥饿:考虑业务场景,尽量避免长时间等待写锁导致的写锁饥饿问题。

ReentrantReadWriteLock适用于读多写少、对读取性能要求较高的场景,但在高并发写入的情况下可能存在写锁饥饿问题,需要谨慎使用。在实际使用中需要根据业务特点和需求综合考虑,选择最适合的锁机制。

你可能感兴趣的:(java,后端)