读写锁

作者: high_m | 来源:发表于2017-09-10 00:28 被阅读0次

    读写锁用于读较写相对频繁的场景,多个读线程可以同时获取读锁而不阻塞,只有一个线程能获取写锁。写锁获取的前提是读锁已经全部被释放,写锁获取后读锁不能再获取(其实是不能被非获取写锁的线程所获取),直到写锁被释放。
    jdk中实现Lock接口的读写锁ReentrantReadWriteLock是如何实现此功能呢?在阅读这篇文章之前,我假设你已经读过可重入锁,对锁的实现有一定的了解。


    读写锁同步状态的设计

    同步状态是一个volatile修饰的int变量,四个字节,前2个字节用来用来标识读状态,后两个字节用来标识写状态。图示如下:

    读写锁状态划分方式(引自并发编程的艺术)

    图中的示例表示当前线程获取了三次写锁(重进入两次),同时也连续获取了两次读锁。根据图示能够轻易得出结论,当合状态不为0,而写状态为0时,则读状态大于0,读锁已被获取。

    写锁的获取与释放

    写锁是一个支持重进入的排他锁。当前线程如果已经获取了写锁,则增加写状态;如果当前线程在获取写锁时,读锁已经被获取或者当前线程不是已经获取写锁的线程,则失败进入等待状态。

            protected final boolean tryAcquire(int acquires) {
                /*
                 * Walkthrough:
                 * 1. If read count nonzero or write count nonzero
                 *    and owner is a different thread, fail.
                 * 2. If count would saturate, fail. (This can only
                 *    happen if count is already nonzero.)
                 * 3. Otherwise, this thread is eligible for lock if
                 *    it is either a reentrant acquire or
                 *    queue policy allows it. If so, update state
                 *    and set owner.
                 */
                Thread current = Thread.currentThread();
                int c = getState();
                int w = exclusiveCount(c);
                if (c != 0) {
                    // (Note: if c != 0 and w == 0 then shared count != 0)
                    //w==0得出读状态不为0
                    if (w == 0 || current != getExclusiveOwnerThread())
                        return false;
                    if (w + exclusiveCount(acquires) > MAX_COUNT)
                        throw new Error("Maximum lock count exceeded");
                    // Reentrant acquire
                    setState(c + acquires);
                    return true;
                }
                //当前节点在写同步队列里是否有前驱节点
                if (writerShouldBlock() ||
                    !compareAndSetState(c, c + acquires))
                    return false;
                setExclusiveOwnerThread(current);
                return true;
            }
    

    写锁的释放与可重入锁基本相似,写状态为0时,释放成功;读写线程能够继续访问读写锁,同时上次写线程的修改对后续读写线程可见。

    读锁的获取与释放

    获取读锁失败的原因:

    • 写锁已被获取且当前线程不是获取写锁的线程。

    获取读锁成功的原因:

    • 写锁已被获取且是当前线程获取的。
    • 写锁未被获取。

    获取读锁的代码较为复杂,因为增加了得到当前线程获取读锁次数的功能(利用ThreadLocal实现)。有兴趣的你可以直接查看源码,这里暂不做说明。


    锁降级

    主要阐述两个方面,锁降级是什么概念?锁降级是否有必要?

    锁降级是什么概念?线程A获取到写锁后,修改数据后再获取读锁,最后释放写锁,这就是锁降级。造成的直接影响就是线程A释放写锁后,下一个企图获取写锁的线程不会立即获取到写锁,因为至少已经有一个线程A读锁还没有释放。

    必要性是有的。如果获取写锁后,你修改了数据,且你希望你修改的数据能被感知到(至少自己是能够感知的),希望线程对数据的变化是极其敏感的,你就一定要在使用读写锁时锁降级。

    举个例子。 线程A获取写锁往小王的账号里打了500块钱,释放写锁。线程C获取读锁要把这条打款消息发送到小王手机(XX往你的银行账号XX里打了XX钱)。如果你不使用锁降级,那么线程B可能会在线程A释放写锁后立即获取到写锁,它也给小王打了500块钱,线程B释放写锁后,线程C获取到读锁给小王发信息(YY往你的银行账号YY里打了YY钱),因为线程A修改的数据没有读线程获取读锁进行感知处理,可能会漏掉这一修改信息,直接影响就是小王少收了一条短信。当然,这种必要性是跟实际业务有关的,如果短信信息只是通知小王银行账号里的总额的话,似乎锁降级也没有什么必要。

    相关文章

      网友评论

        本文标题:读写锁

        本文链接:https://www.haomeiwen.com/subject/wwmajxtx.html