读写锁ReentrantReadWriteLock原理

出现线程安全问题的核心要素是什么?无外乎多个线程同时操作共享资源,那么为了避免出现线程问题只需要回避该要素,我们通常最先想到的方案是什么?加把锁!这样是能解决线程的安全问题,但是又会使程序的性能下降,遇到这样的问题该怎么办呢?

仔细分析多个线程同时操作共享资源这句话,会发现其中有同时操作共享资源这个操作,那么问题就明显了,我多个线程同时改变这个共享资源会出现线程安全问题,但是多个线程同时读取共享资源也会出现安全问题吗?
这样细化下来我们发现,不论对共享资源是修改还是读取都加锁,会损失性能,那么我们可不可以对锁进行细分,使读-读可并发,由此就引出了该文章,读写锁ReentrantReadWriteLock

图解流程

在这里插入图片描述

写锁上锁流程

static final class NonfairSync extends Sync {
    // ...省略无关代码
//外部类WriteLock方法,方便阅读,放在此处 
    public void lock() {
        sync.acquire(1);
    }

    // AQS继成来的方法,方便阅读,放此处 
    public final void acquire(int arg) {
        if (
            //尝试获閃锁失败
                !tryAcquire(arg) &&
                        //将当前线程关联到一个Node对象上,模式为独占模式
                        //进入AQS队列阻塞 
                        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
        ) (
                selfInterrupt();
    }

}

    // Sync继承过来的方法,方便阅读,放此处 
    protected final boolean tryAcquire(int acquires) {
        //获得低16位,代表写锁的state计数
        Thread current = Thread.currentThread();
        int c = getState();
        int w = exclusiveCount(c);
        if (c != 0) {
            if (
                //c != 0 and w == 0表示有读锁,或者
                    w == 0 ||
                            // 如果 exclusiveOwnerThread 不是自己
                            current != getExclusiveOwnerThread()
            ) {
                //获得锁趣
                return false;
            }
            //写锁计数超过低16位,报异常
            if (w + exclusiveCount(acquires) > MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            //写锁重入,获得锁藏 
            setState(c + acquires);
            return true;
        }
        if (
            //判断写锁是否该阻塞,或者
                writerShouldBlock() ||
                        //尝试更改计録败
                        !compareAndSetState(c, c + acquires)
        ) {
            //获得锁趣
            return false;
        }
        //获得锁翊
        setExclusiveOwnerThread(current);
        return true;
    }

    //非公平锁writerShouldBlock总是返回false,无需阻塞
    final boolean writerShouIdBlock() {
        return false;
    }
}

写锁释放流程

static final class NonfairSync extends Sync {
    // ...省略无关代码
    // WriteLock方法,方便阅读,放在此处
    public void unlock() {
        sync.release(l);
    }

    // AQS继迎来的方法,方便阅读,放醐处
    public final boolean release(int arg) {
        //尝试释放写锁成功
        if (tryRelease(arg)) {
            // unpark AQS中等待的线程
            Node h = head;
            if (h != null && h.waitStatus != 0) unparkSuccessor(h);
            return true;
        }
        return false;
    }

    // Sync继承过来的方法,方便阅读,放此处 
    protected final boolean tryRelease(int releases) {
        if (!isHeldExclusively())
            throw new IHegalMonitorStateException();
        int nextc = getState() - releases;
        //因为可重入的原因,写锁计数为0 才算释放成功 
        boolean free = exclusiveCount(nextc) == 0;
        if (free) {
            setExclusiveOwnerThread(null);
        }
        setState(nextc);
        return free;
    }
}

读锁上锁流程

static final class NonfairSync extends Sync {
    // ReadLock方法,方便阅读,放在此处 
    public void lock() {
        sync.acquireShared(1);
    }

    // AQS继迎来的方法,方便阅读,放醐处 
    public final void acquireShared(int arg) {
        // tryAcquireShared返回负数,表示获取读锁失败 
        if (tryAcquireShared(arg) < 0) {
            doAcquireShared(arg);
        }
    }

    // Sync继承演的方法,方便阅读,放此处
    protected final int tryAcquireShared(int unused) {
        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)
        ) {
            //...省略不重要的代码
            return 1;
        }
        return fullTryAcquireShared(current);
    }

    //非公平锁readerShouldBlock看AQS队列中第—个节点是否是写锁
    // true则该阻塞,false则不阻塞
    final boolean readerShouIdBlock() {
        return apparentlyFirstQueuedIsExclusive();
    }

    // AQS继承来的方法,方便阅读,放此处
//与tryAcquireShared功能类似,但会不断尝试for (;;)获取读锁,执行过程中无阻塞
    final int fullTryAcquireShared(Thread current) {
        HoldCounter rh = null;
        for (; ; ) {
            int c = getState();
            if (exclusiveCount(c) != 0) {
                if (getExclusiveOwnerThread() != current)
                    return -1;
            } else if (readerShouldBlock()) {
                // ...省略不重要的代码
            }
            if (sharedCount(c) == MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            if (compareAndSetState(c, c + SHARED_UNIT)) {
                // ...省略不重要的代码
                return 1;
            }
        }
    }

    // AQS继承来的方法,方便阅读,放此处
    private void doAcquireShared(int arg) {
        //将当前线程关联到一个Node对象上,模式为共享模式 
        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) {
                        //㈠
                        // r表示可用资源数,在这里总是1允许传播
                        // (唤醒AQS中下一个Share节点)
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (
                    //是否在获取读锁失败时阻塞(前—个阶段waitStatus == Node.SIGNAL ) 
                        shouldParkAfterFailedAcquire(p, node) &&
                                // park当前线程
                                parkAndChecklnterrupt()
                ) {
                    interrupted = true;
                }
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    //㈠AQS继承过来的方法,方便阅读,放在此处
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        //设置自己为head
        setHead(node);
        // propagate表示有共享资源(例如共享读锁或信号量)
        // 原 head waitStatus == Node.SIGNAL 或 Node.PROPAGATE
        // 在 head waitStatus == Node.SIGNAL 或 Node.PROPAGATE
        if (propagate > 0 || h == null || h.waitStatus < 0 |
                (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //如果是最后一节点或者是等待共享读锁的节点
            if (s == null || s.isShared()) {
                //进入㈡
                doReleaseShared();
            }
        }
    }

    //㈡AQS继迎来的方法,方便阅读,放在此处
    private void doReleaseShared() {
// 如果 head.waitStatus == Node.SIGNAL ==> 0 成功,下一个节点 unpark
// 如果 head.waitStatus == 0 ==> Node.PROPAGATE,为了解决 bug,见后面分析
        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
                    // 下一个节点 unpark 如果成功获取读锁
                    // 并且下下个节点还是shared,继续doReleaseShared
                    unparkSuccessor(h);
                } else if (ws == 0 &&
                        !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

读锁释放流程

static final class NonfairSync extends Sync {
    // ReadLock方法,方便阅读,放在此处 
    public void unlock() {
        sync.releaseShared(1);
    }

    // AQS继迎来的方法,方便阅读,放醐处
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

    // Sync继承演的方法,方便阅读,放此处
    protected final boolean tryReleaseShared(int unused) {
        // ...省略不重要的代码
        for (; ; ) {
            int c = getState();
            int nextc = c - SHAREDJJNIT;
            if (compareAndSetState(c, nextc)) {
            //读锁的诚不会影响其它获取读锁线程,但会影响其它获取写锁线程 
            // 诚为0才是真正释放
                return nextc == 0;
            }
        }
    }

    // AQS继承来的方法,方便阅读,放此处
    private void doReleaseShared() {
        // 如果 head.waitStatus == Node.SIGNAL ==> 0 成功,下―个节点 unpark
        // 如果 head.waitStatus == 0 ==> Node.PROPAGATE 
        for (; ; ) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                //如果有其它线程也在释放读锁,那么需要将waitStatus先改为0
                //防止unparkSuccessor被多涓丸行
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;    // loop to recheck cases
                    unparkSuccessor(h);
                }
                //如果已经是。了,改为-3 ,用来解决传播性,见后文信号量bug分析 
                else if (ws == 0 &&
                        !compareAndSetWaitStatus(h, Node.PROPAGATE)) continue;    // loop on failed CAS
            }
            if (h == head)    // loop if head changed
                break;
        }
    }
}

注意事项

  • 读锁不支持条件变量
  • 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致写锁永久等待
  • 重入时降级支持:即持有写锁的情况下去获取读锁

进一步优化读

StampedLock
该类自JDK8加入,是为了进一步优化读性能,大的特点是在使用读锁、写锁时配合使用【戳】

加解读锁

long stamp = lock.readLock();
lock.unlockRead(stamp);

加解写锁

long stamp = lock.writeLock();
lock.unlockWrite(stamp);

乐观读
StampedLock支持tryOptimisticRead()方法(乐观读),读取完毕后需做一次戳校验如果校验通过,表示改期间确实没有写操作,数据可以安全使用,如果校验未通过,则需要重新获取读锁,保证数据安全

long stamp = lock.tryOptimisticRead();
//验戳
if(!lock.validate(stamp)){
//锁升级
}

以上就是该文章的全部内容
下一篇:Semaphore、CountdownLatch、CyclicBarrier

该文章是作者学习juc所整理的笔记主要的知识来源是黑马的juc系列,如有问题欢迎指正

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值