前言:在JUC ReentrantReadWriteLock是基于AQS实现的读写锁实现。

ReadWriteLock中定义了读写锁需要实现的接口,具体定义如下:

public interface ReadWriteLock {
    //创建一个读锁
    Lock readLock();
    //创建一个写锁
    Lock writeLock();
}

一、ReadWriteLock读写锁适用场景

在ReentrantLock中,线程之间的同步都是互斥的,不管是读操作还是写操作,但是在一些场景中读操作是可以并行进行的,只有写操作才是互斥的,这种情况虽然也可以使用ReentrantLock来解决,但是在性能上也会损失,ReadWriteLock就是用来解决这个问题的。

重入锁ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,但是在大多数场景下,大部分时间都是提供读服务,而写服务占有的时间较少。然而读服务不存在数据竞争问题,如果一个线程在读时禁止其他线程读势必会导致性能降低。所以就提供了读写锁。

读写锁维护着一对锁,一个读锁和一个写锁。通过分离读锁和写锁,使得并发性比一般的排他锁有了较大的提升:在同一时间可以允许多个读线程同时访问,但是在写线程访问时,所有读线程和写线程都会被阻塞。

读写锁适用于读多写少的情况。

二、ReadWriteLock读写锁

读写锁的主要特性:

  1. 公平性:支持公平性和非公平性。
  2. 重入性:支持重入。读写锁最多支持65535个递归写入锁和65535个递归读取锁。
  3. 锁降级:遵循获取写锁、获取读锁在释放写锁的次序,写锁能够降级成为读锁

读写锁ReentrantReadWriteLock实现接口ReadWriteLock,该接口维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。

ReadWriteLock定义了两个方法。readLock()返回用于读操作的锁,writeLock()返回用于写操作的锁。

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

三、ReentrantReadWriteLock可重入读写锁

    /** 内部类  读锁 */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** 内部类  写锁 */
    private final ReentrantReadWriteLock.WriteLock writerLock;

    final Sync sync;

    /** 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock */
    public ReentrantReadWriteLock() {
        this(false);
    }
    /** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */
    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; }

    abstract static class Sync extends AbstractQueuedSynchronizer{
        /**
         * 省略其余源代码
         */
    }
    public static class WriteLock implements Lock, java.io.Serializable{
        /**
         * 省略其余源代码
         */
    }

    public static class ReadLock implements Lock, java.io.Serializable {
        /**
         * 省略其余源代码
         */
    }

ReentrantReadWriteLock与ReentrantLock一样,其锁主体依然是Sync,它的读锁、写锁都是依靠Sync来实现的。所以ReentrantReadWriteLock实际上只有一个锁,只是在获取读取锁和写入锁的方式上不一样而已,它的读写锁其实就是两个类:ReadLock、writeLock,这两个类都是lock实现。

在ReentrantLock中使用一个int类型的state来表示同步状态,该值表示锁被一个线程重复获取的次数。但是读写锁ReentrantReadWriteLock内部维护着两个一对锁,需要用一个变量维护多种状态。所以读写锁采用“按位切割使用”的方式来维护这个变量,将其切分为两部分,高16为表示读,低16为表示写。分割之后,读写锁是如何迅速确定读锁和写锁的状态呢?通过为运算。假如当前同步状态为S,那么写状态等于 S & 0x0000FFFF(将高16位全部抹去),读状态等于S >>> 16(无符号补0右移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;

static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

(1)写锁

写锁就是一个支持可重入的排他锁。

写锁的获取:

写锁的获取最终会调用tryAcquire(int arg),该方法在内部类Sync中实现:

protected final boolean tryAcquire(int acquires) {
        Thread current = Thread.currentThread();
        //当前锁个数
        int c = getState();
        //写锁
        int w = exclusiveCount(c);
        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");
            setState(c + acquires);
            return true;
        }
        //是否需要阻塞
        if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
            return false;
        //设置获取锁的线程为当前线程
        setExclusiveOwnerThread(current);
        return true;
}

该方法和ReentrantLock的tryAcquire(int arg)大致一样,在判断重入时增加了一项条件:读锁是否存在。因为要确保写锁的操作对读锁是可见的,如果在存在读锁的情况下允许获取写锁,那么那些已经获取读锁的其他线程可能就无法感知当前写线程的操作。因此只有等读锁完全释放后,写锁才能够被当前线程所获取,一旦写锁获取了,所有其他读、写线程均会被阻塞。

写锁的释放:

获取了写锁用完了则需要释放,WriteLock提供了unlock()方法释放写锁:

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

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

写锁的释放最终还是会调用AQS的模板方法release(int arg)方法,该方法首先调用tryRelease(int arg)方法尝试释放锁,tryRelease(int arg)方法为读写锁内部类Sync中定义了,如下:

protected final boolean tryRelease(int releases) {
        //释放的线程不为锁的持有者
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        int nextc = getState() - releases;
        //若写锁的新线程数为0,则将锁的持有者设置为null
        boolean free = exclusiveCount(nextc) == 0;
        if (free)
            setExclusiveOwnerThread(null);
        setState(nextc);
        return free;
}

写锁释放锁的整个过程和独占锁ReentrantLock相似,每次释放均是减少写状态,当写状态为0时表示 写锁已经完全释放了,从而等待的其他线程可以继续访问读写锁,获取同步状态,同时此次写线程的修改对后续的线程可见

(2)读锁

读锁为一个可重入的共享锁,它能够被多个线程同时持有,在没有其他写线程访问时,读锁总是或获取成功。

获取读锁总结:

  • 如果不存在线程持有写锁,则获取读锁成功。
  • 如果其他线程持有写锁,则获取读锁失败。
  • 如本线程持有写锁,并且不存在等待写锁的其他线程,则获取读锁成功。
  • 如本线程持有写锁,并且存在等待写锁的其他线程,则如果本线程已经持有读锁,则获取读锁成功,如果不能存在读锁,则此次获取读锁失败。

四、锁降级

读写锁有一个特性就是锁降级,锁降级就意味着写锁是可以降级为读锁的,但是需要遵循先获取写锁、获取读锁在释放写锁的次序。注意如果当前线程先获取写锁,然后释放写锁,再获取读锁这个过程不能称之为锁降级,锁降级一定要遵循那个次序。

锁降级中读锁的获取释放为必要?肯定是必要的。试想,假如当前线程A不获取读锁而是直接释放了写锁,这个时候另外一个线程B获取了写锁,那么这个线程B对数据的修改是不会对当前线程A可见的。如果获取了读锁,则线程B在获取写锁过程中判断如果有读锁还没有释放则会被阻塞,只有当前线程A释放读锁后,线程B才会获取写锁成功。

参考链接

本文主要整理自互联网,主要是便于自己复习知识所用,侵权联系删除,以下为原文参考链接!

【1】JUC解析-ReadWriteLock

【2】【死磕Java并发】—–J.U.C之读写锁:ReentrantReadWriteLock

【3】JUC 可重入 读写锁 [ReentrantReadWriteLock]

ReadWriteLock读写锁(八)的更多相关文章

  1. 12. ReadWriteLock 读写锁

    package com.gf.demo11; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent. ...

  2. java多线程 -- ReadWriteLock 读写锁

    写一条线程,读多条线程能够提升效率. 写写/读写 需要“互斥”;读读 不需要互斥. ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作.只要没有 writer,读取锁 ...

  3. GUC-9 ReadWriteLock : 读写锁

    import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWrit ...

  4. ReadWriteLock: 读写锁

    ReadWriteLock: 读写锁 ReadWriteLock: JDK1.5提供的读写分离锁,采用读写锁分离可以有效帮助减少锁竞争. 特点: 1).使用读写锁.当线程只进行读操作时,可以允许多个线 ...

  5. 22.ReadWriteLock读写锁

    import java.util.Random; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.R ...

  6. 【转】java并发编程系列之ReadWriteLock读写锁的使用

    前面我们讲解了Lock的使用,下面我们来讲解一下ReadWriteLock锁的使用,顾明思义,读写锁在读的时候,上读锁,在写的时候,上写锁,这样就很巧妙的解决synchronized的一个性能问题:读 ...

  7. Java多线程之ReadWriteLock读写锁简介与使用教程

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6558073.html  普通的锁在对某一内容加锁后,其他线程是不能访问的.但是我们要考虑这种情况:如果当前加锁 ...

  8. ReadWriteLock 读写锁(读书笔记)

     读写分离锁可以有效的帮助减少锁的竞争,提升系统的效率, 读-读不互斥 读读之间不阻塞 读-写互斥 读阻塞写,写也会阻塞读 写-写互斥 写写阻塞 在系统中,读操作次数远远大于写操作,则读写锁就可以发挥 ...

  9. Java并发:ReadWriteLock 读写锁

    读写锁在同一时刻可以允许多个线程访问,但是在写线程访问,所有的读线程和其他写线程均被阻塞. 读写锁不像 ReentrantLock 那些排它锁只允许在同一时刻只允许一个线程进行访问,读写锁可以允许多个 ...

随机推荐

  1. OmniPlan 3 Pro密钥

    密钥用户名都是youliyuan.OmniPlan 3:HOMJ-QOJH-OIBN-TNIH-HWUN-TEEH-WUNNKWO-HVKB-JAZE-UIHH-XAVY-BEEX-AVYBCRW-M ...

  2. matlab中fix函数,floor函数,ceil函数

    1)fix(n)的意义是取小于n的整数(是向零点舍入的意思是往零的方向上靠),这是一类应用在整数取值上的函数,就如同以前我们所研究的求整问题,例如,fix(pi)=3;fix(3.5)=3;fix(- ...

  3. Java NIO:IO与NIO的区别

    一.概念 NIO即New IO,这个库是在JDK1.4中才引入的.NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多.在Java API中提供了两套N ...

  4. Maven项目运行Junit测试用例 出现ClassNotFound

    Maven 打包命令 clean package 会清空target下的目录 包含 test-classes 目录 这样执行junit的时候,会出现 ClassNotFound的错误 执行下面的命令即 ...

  5. TCP/IP 协议 OSI七层协议

    ------------------你来自何处并不重要,重要的是你要去往何方,人生最重要的不是所站的位置,而是所去的方向.人只要不失去方向,就永远不会失去自己! day 27 # # -------- ...

  6. [loadrunner]通过检查点判定事务是否成功

    //检查点设置语句 //tmp在此时为临时参数  web_reg_find("SaveCount=tmp",   "Text=xxx",   LAST);   ...

  7. c++入门之字符相关入门

    先上代码: # include "iostream" # include "string" //# define BYTE char//注意,这里没有分号,且# ...

  8. echarts x轴 增加滚动条

    charts x轴 增加滚动条 在option 配置项中添加 [ dataZoom 中配置 ] 设置x轴滚动条 效果图: 动态拖动 以下参考代码 dataZoom配置 官网写法 option = { ...

  9. Spring.profile配合Jenkins发布War包,实现开发、测试和生产环境的按需切换

    前两篇不错 Spring.profile实现开发.测试和生产环境的配置和切换 - Strugglion - 博客园https://www.cnblogs.com/strugglion/p/709102 ...

  10. vscode开发中绝对让你惊艳的插件!!!(个人在用)

    识别模版引擎 1.Apache Velocity :识别Velocity(vm) 2.Art Template Helper:识别artTemplate 点击路径跳转 1.Laravel goto v ...