Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ReentrantReadWriteLock读写锁

ReentrantReadWriteLock读写锁

作者头像
leobhao
发布于 2022-06-28 10:45:40
发布于 2022-06-28 10:45:40
56700
代码可运行
举报
文章被收录于专栏:涓流涓流
运行总次数:0
代码可运行

ReentrantReadWriteLock 结构

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final Sync sync;

// 构造函数
public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

// 读锁
public static class ReadLock implements Lock, java.io.Serializable {
    private final Sync sync;

    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

   public void lock() {
        // 共享模式
        sync.acquireShared(1);
   }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }


    public boolean tryLock() {
        return sync.tryReadLock();
    }


    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }


    public void unlock() {
        sync.releaseShared(1);
    }


    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }
    
}

// 写锁
public static class WriteLock implements Lock, java.io.Serializable {
    private final Sync sync;

    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

    public void lock() {
        // 独占模式
        sync.acquire(1);
    }


    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }


    public boolean tryLock( ) {
        return sync.tryWriteLock();
    }

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }


    public void unlock() {
        sync.release(1);
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

}

// sync 继承于 AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
    ......
}


static final class FairSync extends Sync {
    final boolean writerShouldBlock() {
        return hasQueuedPredecessors();
    }
    final boolean readerShouldBlock() {
        return hasQueuedPredecessors();
    }
}

static final class NonfairSync extends Sync {
    final boolean writerShouldBlock() {
        return false; // writers can always barge
    }
    final boolean readerShouldBlock() {
        return apparentlyFirstQueuedIsExclusive();
    }
}

这里可以看到:

  1. 读、写锁分别对应 ReadLockWriteLock 两个实例
  2. 读、写锁内部使用了同一个 Sync 实例, 分为公平模式和非公平模式
  3. Sync 实例继承于 AQS
  4. ReadLock 使用共享模式, WriteLock使用独占模式
独占模式

看看 WriteLock 的独占模式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// AQS 独占方式
public void lock() {
    sync.acquire(1);
}
// aquire 过程
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  1. tryAcquire 判断 state,为0则直接占有
  2. acquireQueued 将当前节点进入阻塞队列,并挂起当前线程, 等待前驱节点唤醒
  3. 被唤醒后将自己设为head,并将state设为1

这就是一个普通的AQS的独占模式的流程

共享模式

ReadLock 的共享模式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void lock() {
    sync.acquireShared(1);
}

public final void acquireShared(int arg) {
    // 小于0代表没有获取到共享锁, 步骤1
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

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) {
                // 步骤3
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    // 步骤4
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 步骤2
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

(1) tryAcquireShared 小于 0 代表没有获取到共享锁 (2) doAcquireShared 将当前节点进入阻塞队列中等待被唤醒,步骤2是挂起自己 (3) 被唤醒后就可以拿到共享锁了, 步骤三 (4) 然后 setHeadAndPropagate 唤醒其他线程

这里注意独占模式和共享模式的区别:

  • 对于独占模式来说,通常就是 0 代表可获取锁,1 代表锁被别人获取了,重入例外
  • 而共享模式下,每个线程都可以对 state 进行加减操作(独占模式操作state的时候会判断当前线程是否是站有锁的线程)

原理分析

ReadWriteLock 中 state 同时被独占模式和共享模式操作,实现的手段是将 state 这个 32 位的 int 值分为高 16 位和低 16位,分别用于共享模式和独占模式。

sync 类

直接看关键的内部Sync类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
abstract static class Sync extends AbstractQueuedSynchronizer {
    // 下面这块说的就是将 state 一分为二,高 16 位用于共享模式,低16位用于独占模式
    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;
    // 取 c 的高 16 位值,代表读锁的获取次数(包括重入)
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    // 取 c 的低 16 位值,代表写锁的重入次数,因为写锁是独占模式
    static int exclusiveCount(  int c) { return c & EXCLUSIVE_MASK; }

    // 这个嵌套类的实例用来记录每个线程持有的读锁数量(读锁重入)
    static final class HoldCounter {
        // 持有的读锁数
        int count = 0;
        // 线程 id
        final long tid = getThreadId(Thread.currentThread());
    }

    // ThreadLocal 的子类
    static final class ThreadLocalHoldCounter
        extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }
    /**
      * 组合使用上面两个类,用一个 ThreadLocal 来记录当前线程持有的读锁数量
      */ 
    private transient ThreadLocalHoldCounter readHolds;

    // 用于缓存,记录"最后一个获取读锁的线程"的读锁重入次数,
    // 所以不管哪个线程获取到读锁后,就把这个值占为已用,这样就不用到 ThreadLocal 中查询 map 了
    // 这个是用于提升性能
    private transient HoldCounter cachedHoldCounter;

    // 第一个获取读锁的线程(并且其未释放读锁),以及它持有的读锁数量 
    // 这个也是为了提升性能
    private transient Thread firstReader = null;
    private transient int firstReaderHoldCount;

    Sync() {
        // 初始化 readHolds 这个 ThreadLocal 属性,用来记录该线程读锁的次数
        readHolds = new ThreadLocalHoldCounter();
        // 为了保证 readHolds 的内存可见性
        setState(getState());
    }
    ...
}
  1. state 的高 16 位代表读锁的获取次数,包括重入次数,获取到读锁一次加 1,释放掉读锁一次减 1。
  2. state 的低 16 位代表写锁的获取次数,因为写锁是独占锁,同时只能被一个线程获得,所以它代表重入次数
  3. 每个线程都需要维护自己的HoldCounter,记录该线程获取的读锁次数,这样才能知道到底是不是读锁重入,用 ThreadLocal 属性readHolds维护
读锁获取
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ReadLock
public void lock() {
    sync.acquireShared(1);
}
// AQS
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

这一切都是AQS的流程,Sync实现了tryAcquireShared, 再AQS的定义中,tryAcquireShared返回值小于0代表没有获取到共享锁,大于0代表获取到了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected final int tryAcquireShared(int unused) {

    Thread current = Thread.currentThread();
    int c = getState();

    // exclusiveCount(c) 不等于 0,说明有线程持有写锁,
    //    而且不是当前线程持有写锁,那么当前线程获取读锁失败
    //         (另,如果持有写锁的是当前线程,是可以继续获取读锁的)
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;

    // 读锁的获取次数
    int r = sharedCount(c);

    // 读锁获取是否需要被阻塞
    if (!readerShouldBlock() &&
        // 判断是否会溢出
        r < MAX_COUNT &&
        // 下面这行 CAS 是将 state 属性的高 16 位加 1,低 16 位不变,如果成功就代表获取到了读锁
        compareAndSetState(c, c + SHARED_UNIT)) {

        // =======================
        //   进到这里就是获取到了读锁
        // =======================

        if (r == 0) {
            // r == 0 说明此线程是第一个获取读锁的,或者说在它前面获取读锁的都释放了
            //  记录 firstReader 为当前线程,及其持有的读锁数量:1
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            // 进来这里,说明是 firstReader 重入获取读锁(这非常简单,count 加 1 结束)
            firstReaderHoldCount++;
        } else {
            // 前面我们说了 cachedHoldCounter 用于缓存最后一个获取读锁的线程
            // 如果 cachedHoldCounter 缓存的不是当前线程,设置为缓存当前线程的 HoldCounter
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0) 
                // 到这里,那么就是 cachedHoldCounter 缓存的是当前线程,但是 count 为 0,
                readHolds.set(rh);

            // count 加 1
            rh.count++;
        }
        // return 大于 0 的数,代表获取到了共享锁
        return 1;
    }
    // 往下看
    return fullTryAcquireShared(current);
}

如果要执行fullTryAcquireShared, 就不能进入if分支,则需要readerShouldBlock返回true,这里有两种可能: (1) 在 FairSync 中 hasQueuedPredecessors()(公平模式有其他节点再等待锁,不能直接就获取锁) (2 在 NonFairSync 中apparentlyFirstQueuedIsExclusive(),即判断阻塞队列中head的第一个后继节点是否是来获取写锁的,如果是的话,让这个写锁先来,避免写锁饥饿(这里是给了写锁更高的优先级,所以如果碰上获取写锁的线程马上就要获取到锁了,获取读锁的线程不应该和它抢。如果head.next 不是写锁,那么就随便抢了,因为是非公平模式)

另外还有一种情况就是compareAndSetState(c, c + SHARED_UNIT)的CAS操作失败了也会进入fullTryAcquireShared,这代表着操作state存在竞争,可能是读锁竞争也可能是写锁竞争

接下来就是fullTryAcquireShared的流程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    // 这里 for 循环
    for (;;) {
        int c = getState();
        // 如果其他线程持有了写锁,自然这次是获取不到读锁了,返回-1,执行 doAcquireShared 阻塞排队
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        } else if (readerShouldBlock()) {
            /**
              * 进来这里,说明:
              *  1. exclusiveCount(c) == 0:写锁没有被占用
              *  2. readerShouldBlock() 为 true,说明阻塞队列中有其他线程在等待(或下一个是写锁)
              * 既然有其他再等待,就需要执行阻塞操作了
              */
            // firstReader 线程重入读锁,直接到下面的 CAS
            if (firstReader == current) {
                
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        // cachedHoldCounter 缓存最后一个读锁的holderCount
                        // cachedHoldCounter 缓存的不是当前线程
                        // 那么到 ThreadLocal 中获取当前线程的 HoldCounter
                        // 如果当前线程从来没有初始化过 ThreadLocal 中的值,get() 会执行初始化
                        rh = readHolds.get();
                        // 如果发现 count == 0,也就是说,纯属上一行代码初始化的,那么执行 remove
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    // 返回 -1 执行阻塞排队
                    return -1;
            }
        }
        // 判断 state 是否溢出
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
         // 这里 CAS 成功,那么就意味着成功获取读锁了
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            // 下面需要做的是设置 firstReader 或 cachedHoldCounter
            if (sharedCount(c) == 0) {
                // 如果发现 sharedCount(c) 等于 0,就将当前线程设置为 firstReader
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                // 下面这几行,就是将 cachedHoldCounter 设置为当前线程
                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;
            }
            // 返回大于 0 的数,代表获取到了读锁
            return 1;
        }
    }
}
读锁的释放
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ReadLock
public void unlock() {
    sync.releaseShared(1);
}
// Sync
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared(); // 这句代码其实唤醒 获取写锁的线程,往下看就知道了
        return true;
    }
    return false;
}

// Sync
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) {
        if (firstReaderHoldCount == 1)
            // firstReader 代表当前第一个获取读锁的线程
            // 如果等于 1,那么这次解锁后就不再持有锁了,把 firstReader 置为 null,给后来的线程用
            firstReader = null;
        else
            // 重入次数减一
            firstReaderHoldCount--;
    } else {
        // 判断 cachedHoldCounter 是否缓存的是当前线程,不是的话要到 ThreadLocal 中取
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();

        int count = rh.count;
        if (count <= 1) {
            // 这一步将 ThreadLocal remove 掉,防止内存泄漏。因为已经不再持有读锁了
            readHolds.remove();
            if (count <= 0)
                // 就是那种,lock() 一次,unlock() 好几次的逗比
                throw unmatchedUnlockException();
        }
        // count 减 1
        --rh.count;
    }
    // 保证cas操作成功
    for (;;) {
        int c = getState();
        // nextc 是 state 高 16 位减 1 后的值,也就是释放一次读锁的值
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // 如果 nextc == 0,那就是 state 全部 32 位都为 0,也就是读锁和写锁都空了
            // 此时这里返回 true 的话,其实是帮助唤醒后继节点中的获取写锁的线程
            return nextc == 0;
    }
}

读锁的释放过程 (1) 将 hold count 减 1,如果减到 0 的话,还要将 ThreadLocal 中的 remove 掉防止内存泄漏 (2) 在 for 循环中将 state 的高 16 位减 1,如果发现读锁和写锁都释放光了,那么唤醒后继的获取写锁的线程

写锁的获取

写锁是独占的,如果发现有读锁占用也是要阻塞等待的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// WriteLock
public void lock() {
    sync.acquire(1);
}
// AQS
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        // 如果 tryAcquire 失败,那么进入到阻塞队列等待
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// Sync
protected final boolean tryAcquire(int acquires) {

    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {

        // 看下这里返回 false 的情况:
        //   c != 0 && w == 0: 写锁可用,但是有线程持有读锁(也可能是自己持有)
        //   c != 0 && w !=0 && current != getExclusiveOwnerThread(): 其他线程持有写锁
        //   也就是说,只要有读锁或写锁被占用,这次就不能获取到写锁
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;

        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");

        // 这里不需要 CAS,仔细看就知道了,能到这里的,只可能是写锁重入,不然在上面的 if 就拦截了
        setState(c + acquires);
        return true;
    }

    // 如果写锁获取不需要 block,那么进行 CAS,成功就代表获取到了写锁
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

writerShouldBlock 的判断: (1) 如果非公平锁永远返回 false, 因为非公平模式永远不需要排队,直接 CAS 尝试获取锁就行 (2) 公平模式还是hasQueuedPredecessors, 判断是否有线程排队

写锁的释放
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/ WriteLock
public void unlock() {
    sync.release(1);
}

// AQS
public final boolean release(int arg) {
    // 1. 释放锁
    if (tryRelease(arg)) {
        // 2. 如果独占锁释放"完全",唤醒后继节点
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// Sync 
// 释放锁,是线程安全的,因为写锁是独占锁,具有排他性
// 实现很简单,state 减 1 就是了
protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    // 如果 exclusiveCount(nextc) == 0,也就是说包括重入的,所有的写锁都释放了,
    // 那么返回 true,这样会进行唤醒后继节点的操作。
    return free;
}

独占锁的释放很简单,直接state减1就好

StampedLock

ReadWriteLock 可以解决多线程读写的问题, 但是读的时候, 写线程需要等待读线程释放了才能获取写锁,即读的时候不能写,这是一种悲观的策略。

jdk8 引入了新的读写锁:StampedLock, 进一步提升了并发执行效率。

StampedLockReadWriteLock相比,改进之处在于:读的过程中也允许获取写锁后写入。采用的是一种乐观锁的方式去判断。

ReadWriteLoc相比,写入的加锁是完全一样的,不同的是读取。首先通过tryOptimisticRead()获取一个乐观读锁,并返回版本号。接着进行读取,读取完成后通过validate()去验证版本号,如果在读取过程中没有写入,版本号不变,验证成功,就可以放心地继续后续操作。如果在读取过程中有写入,版本号会发生变化,验证将失败。在失败的时候,再通过获取悲观读锁再次读取。由于写入的概率不高,程序在绝大部分情况下可以通过乐观读锁获取数据,极少数情况下使用悲观读锁获取数据。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-03-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java 读写锁 ReentrantReadWriteLock 源码分析
转自:https://www.javadoop.com/post/reentrant-read-write-lock#toc5
Java技术江湖
2019/09/25
2960
ReentrantReadWriteLock读写锁解析
我们之前说到,ReentrantLock是独占锁,某一时刻只有一个线程可以获取该锁,而实际上会存在很多读多写少的场景,而读操作本身并不会存在数据竞争问题,如果使用独占锁,可能会导致其中一个读线程使其他的读线程陷入等待,降低性能。
烂猪皮
2023/09/04
3380
ReentrantReadWriteLock读写锁解析
读写锁——ReentrantReadWriteLock原理详解
说明:可以看到ReentrantReadWriteLock实现了ReadWriteLock接口,ReadWriteLock接口规范了读写锁方法,具体操作由子类去实现,同时还实现了Serializable接口,表示可以进行序列化操作。
须臾之余
2019/07/23
7.8K0
源码分析— java读写锁ReentrantReadWriteLock
今天看Jraft的时候发现了很多地方都用到了读写锁,所以心血来潮想要分析以下读写锁是怎么实现的。
luozhiyun
2019/12/10
3700
【死磕Java并发】-----J.U.C之读写锁:ReentrantReadWriteLock
此篇博客所有源码均来自JDK 1.8 重入锁ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,但是在大多数场景下,大部分时间都是提供读服务,而写服务占有的时间较少。然而读服务不存在数据竞争问题,如果一个线程在读时禁止其他线程读势必会导致性能降低。所以就提供了读写锁。 读写锁维护着一对锁,一个读锁和一个写锁。通过分离读锁和写锁,使得并发性比一般的排他锁有了较大的提升:在同一时间可以允许多个读线程同时访问,但是在写线程访问时,所有读线程和写线程都会被阻塞。 读写锁的主要特性: 公平性
芋道源码
2018/03/02
8120
Java 读写锁 ReentrantReadWriteLock 源码分析
本文内容:读写锁 ReentrantReadWriteLock 的源码分析,基于 Java7/Java8。
慕容千语
2019/06/11
3930
死磕Java并发:J.U.C之读写锁:ReentrantReadWriteLock
重入锁ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,但是在大多数场景下,大部分时间都是提供读服务,而写服务占有的时间较少。然而读服务不存在数据竞争问题,如果一个线程在读时禁止其他线程读势必会导致性能降低。所以就提供了读写锁。
程序猿DD
2018/07/31
2640
死磕Java并发:J.U.C之读写锁:ReentrantReadWriteLock
探索 JUC 之美---可重入读写锁 ReentrantReadWriteLock可重入读写锁 ReentrantReadWriteLock实现AQS只有一个状态,那么如何表示 多个读锁 与 单个写锁
读写锁维护了一对相关的锁,一个用于只读操作,一个用于写入操作。 只要没有writer,读锁可以由多个reader线程同时保持。写锁是独占的。 互斥锁一次只允许一个线程访问共享数据,哪怕进行的是只读操作 读写锁允许对共享数据进行更高级别的并发访问 对于写操作,一次只有一个线程(write线程)可以修改共享数据 对于读操作,允许任意数量的线程同时进行读取。 与互斥锁相比,使用读写锁能否提升性能则取决于读写操作期间读取数据相对于修改数据的频率,以及数据的争用,即在同一时间试图对该数据执行读取或写入操作的线程数
JavaEdge
2018/05/16
9770
Java Review - 并发编程_读写锁ReentrantReadWriteLock的原理&源码剖析
解决线程安全问题使用ReentrantLock就可以,但是ReentrantLock是独占锁,某时只有一个线程可以获取该锁,而实际中会有写少读多的场景,显然ReentrantLock满足不了这个需求,所以ReentrantReadWriteLock应运而生。ReentrantReadWriteLock采用读写分离的策略,允许多个线程可以同时获取读锁。
小小工匠
2021/12/05
2890
Java Review - 并发编程_读写锁ReentrantReadWriteLock的原理&源码剖析
Java读写锁实现原理
最近做的一个小项目中有这样的需求:整个项目有一份config.json保存着项目的一些配置,是存储在本地文件的一个资源,并且应用中存在读写(读>>写)更新问题。既然读写并发操作,那么就涉及到操作互斥,这里自然想到了读写锁,本文对读写锁方面的知识做个梳理。
Java团长
2018/09/27
9980
Java读写锁实现原理
readandwritelock_读写锁使用场景
ReadWriteLock管理一组锁,一个是只读的锁,一个是写锁。读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。 所有读写锁的实现必须确保写操作对读操作的内存影响。换句话说,一个获得了读锁的线程必须能看到前一个释放的写锁所更新的内容。 读写锁比互斥锁允许对于共享数据更大程度的并发。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。ReadWriteLock适用于读多写少的并发情况。 Java并发包中ReadWriteLock是一个接口,主要有两个方法,如下:
全栈程序员站长
2022/09/22
4510
readandwritelock_读写锁使用场景
【原创】Java并发编程系列17 | 读写锁八讲(上)
通过以下几部分来分析Java提供的读写锁ReentrantReadWriteLock:
java进阶架构师
2020/04/07
5820
【原创】Java并发编程系列17 | 读写锁八讲(上)
快进来!花几分钟看一下 ReentrantReadWriteLock 的原理!
" 在看完 ReentrantLock 之后,在高并发场景下 ReentrantLock 已经足够使用,但是因为 ReentrantLock 是独占锁,同时只有一个线程可以获取该锁,而很多应用场景都是读多写少,这时候使用 ReentrantLock 就不太合适了。读多写少的场景该如何使用?在 JUC 包下同样提供了读写锁 ReentrantReadWriteLock 来应对读多写少的场景。 "
程序员小航
2020/11/23
3820
快进来!花几分钟看一下 ReentrantReadWriteLock 的原理!
JAVA面试备战(九)--ReentrantReadWriteLock
ReadLock和WriteLock是ReentrantReadWriteLock的两个内部类,Lock的上锁和释放锁都是通过AQS来实现的。
程序员爱酸奶
2022/04/12
2810
JAVA面试备战(九)--ReentrantReadWriteLock
ReentrantReadWriteLock 可冲入读写锁,锁降级
今天来分析一下读锁的获取和释放过程,读锁相比较写锁要稍微复杂一点,其中还有一点有争议的地方——锁降级。
名字是乱打的
2022/03/04
8210
ReentrantReadWriteLock 可冲入读写锁,锁降级
(juc系列)reentrantreadwritelock源码学习
这个类是一个ReadWriteLock的实现类,实现了类似于ReentrantLock的语义.
呼延十
2021/10/18
3200
深入分析ReentrantReadWriteLock读写锁
今天一起来聊聊ReentrantReadWriteLock,当我们有遇到一写多读的场景时,我们可以用它来提升并发性能。因为它最大的特点就是读读并发,也就是读锁不会阻塞另外的线程获取读锁。如果对ReentrantLock不了解可以先参考这篇文章(深入理解ReentrantLock和AQS),因为写锁的获取和释放就是排他锁,所以流程和ReentrantLock获取锁和释放锁的流程基本一致,本文不会再过多的篇章去分析。
全栈程序员站长
2022/07/04
3910
深入分析ReentrantReadWriteLock读写锁
多线程8 读写锁ReentrantReadWriteLock加解锁
读锁是可并行的,写锁是串行的,那么如果多个读锁并行执行,遇到升级语句,就会出现死锁,比如t1要升级,那么就要等t2释放锁,而t2正好也在当t1释放锁。
用针戳左手中指指头
2021/01/29
4630
ReentrantReadWriteLock源码解析
之前我们学习了ReentrantLock锁的大概实现机制,其中的大boss是AQS,最终我们得出的结论是,AQS维护了锁的绝大多数操作。我们只需要使用它提供的方法即可实现锁的功能。那么ReentrantReadWriteLock(读写锁)是如何实现的呐,按照之前的说法,ReentrantReadWriteLock应该也是借助AQS来做吧,毕竟人家提供了那么多方法,不用白不用么。那么现在我们想一下如果让咋去设计一个读写锁,应该怎么设计?首先我们需要知道什么是读写锁。读写锁解决的什么问题?
写一点笔记
2020/08/25
3230
ReentrantReadWriteLock源码解析
Java读写锁浅析
Java读写锁,也就是ReentrantReadWriteLock,其包含了读锁和写锁,其中读锁是可以多线程共享的,即共享锁,而写锁是排他锁,在更改时候不允许其他线程操作。读写锁底层是同一把锁(基于同一个AQS),所以会有同一时刻不允许读写锁共存的限制。
luoxn28
2021/05/13
3K0
推荐阅读
相关推荐
Java 读写锁 ReentrantReadWriteLock 源码分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档