深入分析实战可重入读写锁ReentrantReadWriteLock

文章目录

    • 前言
    • 加锁规则
    • 同步原理
    • 源码解析
    • 实战演示

前言

前面我们学习了可重入锁ReentrantLock,可重入锁是一个排他锁,只要不是当前线程访问加锁资源都不能够进入,只能等待锁的释放。当然,这种加锁方式也有一定的适用场景。但是,如果在读多写少的情况下可重入锁ReentrantLock可能不是那么完美,比如缓存的写入和读取。今天,我们就引出可重入读写锁ReentrantReadWriteLock,其读写分离的机制,大大提升缓存场景的系统性能。

加锁规则

可重入锁ReentrantReadWriteLock内部分为读锁和写锁,读锁与写锁的加锁规则如下:

锁类型 读锁 写锁
读锁 共享 互斥
写锁 互斥 互斥

同步原理

可重入锁ReentrantReadWriteLock的同步机制依然是继承抽象同步队列AQS,其内部实现了自身的读锁与写锁的规则,覆写了AQS的一些同步标识和方法。其本质上都是使用AQS同步原理,并用AQS的阻塞队列保存阻塞线程,用AQS的STATE来表示当前锁状态,可重入本质上也是加减STATE来达到对应的目的。

源码解析

进入 package java.util.concurrent.locks 查看可重入ReentrantReadWriteLock源码:

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; }
}

如上源码所示,可重入读写锁实现了读写锁ReadWriteLock,保留读写锁的机制,增加了可重入机制。调用方可以通过传入标识实现非公平锁和公平锁来保证同步,当然可重入读写锁默认NonfairSync非公平锁同步。

继续查看源码:

//内部类继承抽象同步队列
abstract static class Sync extends AbstractQueuedSynchronizer{
       //加解锁省略
}

//公平锁
static final class FairSync extends Sync {
    private static final long serialVersionUID = -2274990926593161451L;
    final boolean writerShouldBlock() {
        return hasQueuedPredecessors();
    }
    final boolean readerShouldBlock() {
        return hasQueuedPredecessors();
    }
}
//非公平锁
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;
    final boolean writerShouldBlock() {
        return false; // writers can always barge
    }
    final boolean readerShouldBlock() {
        /* As a heuristic to avoid indefinite writer starvation,
         * block if the thread that momentarily appears to be head
         * of queue, if one exists, is a waiting writer.  This is
         * only a probabilistic effect since a new reader will not
         * block if there is a waiting writer behind other enabled
         * readers that have not yet drained from the queue.
         */
        return apparentlyFirstQueuedIsExclusive();
    }
}

//获取读锁
public static class ReadLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -5992448646407690164L;
    private final Sync sync;

    /**
     * Constructor for use by subclasses
     *
     * @param lock the outer lock object
     * @throws NullPointerException if the lock is null
     */
    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
       //加解锁省略
}

//写锁
public static class WriteLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -4992448646407690164L;
    private final Sync sync;

    /**
     * Constructor for use by subclasses
     *
     * @param lock the outer lock object
     * @throws NullPointerException if the lock is null
     */
    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
    //加解锁省略
}

如上源码所示,可重入读写锁公平与非公平锁都集成内部类Sync,本质上还是继承至AQS。ReentrantReadWriteLock的读锁与写锁都是实现Lock,并覆写了其增加解锁的方法;对于锁机制的同步规则则是直接传入内部类Sync。

由此可知,可重入读写锁ReentrantReadWriteLock的加锁方法来源是Lock,同步机制来源与抽象同步队列AQS。当然对于可重入读写锁为保证读锁与写锁、写锁与写锁的互斥,覆写了AQS的同步方法,加入了满足自身规则的一些方法。

继续查看获取锁的源码:

//尝试获取共享锁
protected final int tryAcquireShared(int unused) {
    /*
     * 1. 如果是写锁持有资源,其他线程直接返回失败
     * 2. 如果没有资源没有加写锁,则会检查资源的锁定状态,
     并尝试用cas修改state状态
     * 3. 如果第二步失败,则表示当前线程没有获取锁资格或者cas修改state失败,
     则该线程会重试。
     */
    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);
}

/**
 * 重试获取共享锁,相比之前的方法更为简单的验证锁和cas修改state
 */
final int fullTryAcquireShared(Thread current) {

    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        } else if (readerShouldBlock()) {
            // Make sure we're not acquiring read lock reentrantly
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}

如上源码所示,获取共享锁的源码主要是验证当前资源是否已经被加写锁,如果加了写锁其他线程会互斥不能获取该锁;如果没有加写锁则会进行加锁资格验证,读锁则可以共享加锁。在验证当前线程有加锁资格后,程序会用CAS修改STATE状态以保证可重入和释放锁机制的正常运行。

当然,如果当前线程之前是加了写锁,现在该线程对资源加读锁,那么此时当前写锁会进行降级为读锁。但是,反之当前线程加了读锁,然后该线程又对资源加写锁是不被支持的,因为这样会造成死锁。

实战演示

虽然可重入读写锁ReentrantReadWriteLock在实际生产中使用的场景较少,但是还是有它的一席之地的。话说有需要才会存在,可重入读写锁对我们生产的缓存操作极其重要。

以下我将就缓存的读写进行代码演示,当然也是因为引入可重入读写锁会使得缓存操作效率更高。

/**
 * ReentrantReadWriteLockDemo
 * @author senfel
 * @version 1.0
 * @date 2023/5/22 10:48
 */
@Slf4j
public class ReentrantReadWriteLockDemo {

    private static final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    /**
     * 用内存缓存模拟
     */
    private static final Map cache  = new HashMap<>();


    /**
     * 获取缓存
     * @param key
     * @author senfel
     * @date 2023/5/22 14:44
     * @return java.lang.String
     */
    public static String getValue(String key){
        //加读锁获取缓存
        try{
            readWriteLock.readLock().lock();
            String str = cache.get(key);
            if(StringUtils.isNotBlank(str)){
                return str;
            }
        }catch (Exception e){
            log.error("获取缓存异常:{}",e.getStackTrace());
        }finally {
            readWriteLock.readLock().unlock();
        }
        //没有获取到写入缓存,需要加写锁
        try {
            readWriteLock.writeLock().lock();
            //加写锁成功,再次验证是否存在缓存
            String str = cache.get(key);
            if(StringUtils.isNotBlank(str)){
                return str;
            }else{
                //查询数据库获取缓存,这里模拟设置一个常量标识以获取缓存
                str = "senfel";
                cache.put(key,str);
                return str;
            }
        }catch (Exception e){
            log.error("获取缓存异常:{}",e.getStackTrace());
        }finally {
            readWriteLock.writeLock().unlock();
        }
        return null;
    }
    
    /**
     * 设置缓存
     * @param key
     * @author senfel
     * @date 2023/5/22 14:44
     * @return java.lang.String
     */
    public void setValue(String key,String value){
        try{
            readWriteLock.writeLock().lock();
            cache.put(key,value);
        }catch (Exception e){
            log.error("设置缓存异常:{}",e.getStackTrace());
        }finally {
            readWriteLock.writeLock().unlock();
        }
    } 
}

你可能感兴趣的:(并发编程,java,jvm,开发语言,并发编程,lock)