美文网首页
读写锁实现

读写锁实现

作者: 万福来 | 来源:发表于2020-05-07 17:22 被阅读0次

读写锁

ReentrantReadWriteLock可重入读写锁(实现ReadWriteLock接口)

使用:ReentrantReadWriteLock是ReadWriteLock接口的实现类。ReadWriteLock接口的核心方法是readLock(),writeLock()。实现了并发读、互斥写。但读锁会阻塞写锁,是悲观锁的策略。
ReentrantReadWriteLock有5个静态方法:

  • Sync:继承于经典的AbstractQueuedSynchronizer(传说中的AQS),是一个抽象类,包含2个抽象方法readerShouldBlock();writerShouldBlock()
  • FairSync和NonfairSync:继承于Sync,分别实现了公平/非公平锁。
  • ReadLock和WriteLock:都是Lock实现类,分别实现了读、写锁。ReadLock是共享的,而WriteLock是独占的。于是Sync类覆盖了AQS中独占和共享模式的抽象方法(tryAcquire/tryAcquireShared等),用同一个等待队列来维护读/写排队线程,而用一个32位int state标示和记录读/写锁重入次数--Doug Lea把状态的高16位用作读锁,记录所有读锁重入次数之和,低16位用作写锁,记录写锁重入次数。所以无论是读锁还是写锁最多只能被持有65535次。

性能和建议:适用于读多写少的情况。性能较高。

  • 公平性
    非公平锁(默认),为了防止写线程饿死,规则是:当等待队列头部结点是独占模式(即要获取写锁的线程)时,只有获取独占锁线程可以抢占,而试图获取共享锁的线程必须进入队列阻塞;当队列头部结点是共享模式(即要获取读锁的线程)时,试图获取独占和共享锁的线程都可以抢占。
    公平锁,利用AQS的等待队列,线程按照FIFO的顺序获取锁,因此不存在写线程一直等待的问题。
  • 重入性:读写锁均是可重入的,读/写锁重入次数保存在了32位int state的高/低16位中。而单个读线程的重入次数,则记录在ThreadLocalHoldCounter类型的readHolds里。
  • 锁降级:写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级。
  • 锁获取中断:读取锁和写入锁都支持获取锁期间被中断。
  • 条件变量:写锁提供了条件变量(Condition)的支持,这个和独占锁ReentrantLock一致,但是读锁却不允许,调用readLock().newCondition()会抛出UnsupportedOperationException异常。

StampedLock戳锁 一个高性能的读写锁

使用:
StampedLock控制锁有三种模式(排它写,悲观读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问。
原理:
StampedLockd的内部实现是基于CLH(自旋锁队列)锁的,一种自旋锁,保证没有饥饿且FIFO。
CLH锁原理:锁维护着一个等待线程队列,所有申请锁且失败的线程都记录在队列。一个节点代表一个线程,保存着一个标记位locked,用以判断当前线程是否已经释放锁。当一个线程试图获取锁时,从队列尾节点作为前序节点,循环判断所有的前序节点是否已经成功释放锁。
StampedLockd内部维护了一个StampedLockd,源码中的WNote就是等待链表队列,每一个WNode标识一个等待线程,whead为CLH队列头,wtail为CLH队列尾,state为锁的状态。long型即64位,倒数第八位标识写锁状态,如果为1,标识写锁占用!
首先是常量标识:
WBIT=1000 0000(即-128)
RBIT =0111 1111(即127)
SBIT =1000 0000(后7位表示当前正在读取的线程数量,清0)

  1. 乐观读:
    tryOptimisticRead():如果当前没有写锁占用,返回state(后7位清0,即清0读线程数),如果有写锁,返回0,即失败。
  2. 校验stamp:
    校验这个戳是否有效validate():比较当前stamp和发生乐观锁得到的stamp比较,不一致则失败。
  3. 悲观读:
    乐观锁失败后锁升级为readLock():尝试state+1,用于统计读线程的数量,如果失败,进入acquireRead()进行自旋,通过CAS获取锁。如果自旋失败,入CLH队列,然后再自旋,如果成功获得读锁,激活cowait队列中的读线程Unsafe.unpark(),最终依然失败,Unsafe().park()挂起当前线程。
  4. 排它写:
    writeLock():典型的cas操作,如果STATE等于s,设置写锁位为1(s+WBIT)。acquireWrite跟acquireRead逻辑类似,先自旋尝试、加入等待队列、直至最终Unsafe.park()挂起线程。
  5. 释放锁
    unlockWrite():释放锁与加锁动作相反。将写标记位清零,如果state溢出,则退回到初始值;

相关文章

  • 读写锁实现

    读写锁 ReentrantReadWriteLock可重入读写锁(实现ReadWriteLock接口) 使用:Re...

  • Java并发编程-读写锁(ReentrantReadWriteL

    章节目录 ReentrantReadWriteLock 特性 读写锁接口示例 读写锁的实现分析读写状态设计写锁的释...

  • block分析(上)

    读写锁的补充 实现读写锁的两种方案 对底层pthread进行封装 GCD封装 读写锁要实现的功能 多读单写,多读就...

  • Java - ReentrantReadWriteLock的读写

    ReentrantReadWriteLock是可重入读写锁,底层依赖AQS实现,读写锁的竞争通过state的高位和...

  • 17.读写锁ReentrantWriteReadLock

    读写锁ReentrantWriteReadLock,基于AQS的锁机制,实现ReadWriteLock接口。内部有...

  • java锁(7)改进读写锁StampedLock详解

    1、StampedLock特性 StampedLock是JDK 8新增的读写锁,跟读写锁不同,它并不是由AQS实现...

  • 锁2

    5、读写锁 相比Java中的锁(Locks in Java)里Lock实现,读写锁更复杂一些。假设你的程序中涉及到...

  • RWMutex

    前面分析了互斥锁,在针对写少读多的场景,更好的选择是使用读写锁。实现读写锁主要解决下列的问题: 写锁需要阻塞写锁:...

  • 读写锁

    读写锁在 Java 的实现是 ReentrantReadWriteLock,称为可重入读写锁。其与 Reentra...

  • Go 读写锁

    读写锁sync.RWMutext实现读者写者问题

网友评论

      本文标题:读写锁实现

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