java多线程编程-ReentrantReadWriteLock笔记

总结

  1. 两类核心数据结构:第一,针对已经申请到锁的线程的数据结构,记录已经持有的锁数目(重入);第二,针对正在申请中阻塞的线程,会根据先后顺序记录。

    1. 对于第一类数据结构,主要是exclusive排它锁计数器和share共享锁计数器,前者对应写锁,后者对应读锁。
    2. 系统实现上,将两类计数器保存在一个32位变量中,高16位和低16位分别对应读锁和写锁,使用Sync内部类的位操作函数处理。
    3. 对于第二类数据结构,主要是使用了双向链表的队列,同时有一个head。head指向最近申请到lock的线程信息节点。

  1. 尝试获取读锁只有两个入口:第一,调用线程自己调用lock函数会进行一次尝试获取;第二,前者失败后,会将将自己插入队列末尾,并在for(;;)中,head节点可以尝试获取锁(相对公平性),失败会尝试park阻塞。

    1. 对于第二种,如果获取成功,会进行队列操作。
    2. 对于上面说的队列操作,首先会将head设置为自己。其次,如果是读锁lock,会将后续连续的读锁节点序列全部lock,具体来说,会将自己的状态从<0改为0,并调用unpark后续节点(会产生迭代)。
    3. 对于该队列操作,有个竞争态的问题(?),即执行过程中,head指针(不是threadlocal,所以是共享的)有可能被其他线程改了(how?),则需要处理。(注意这是lock的实现,并不是出于synchronized这种区域内部,除了threadlocal和park和unpark等,只要是普通变量,都有可能发生竞争问题。)

  1. 尝试获取读锁函数(优化的tip):

    1. 由于读锁允许多个线程重入累加自己的读锁数目,所以每个线程解锁时需要累减自己的读锁数目,同时,不能多也不能少,所以每个线程需要用threadlocal记录自己持有的读锁数目,对应数据结构A(为什么不用共享的map?性能影响大?)
    2. 性能优化tip(即实现偏向锁):threadlocal的get和set性能较差,如果对于同一个线程不断获取读锁的场景,每次都get性能影响大,需要改进:使用普通变量(共享的)Q记录最近一次获取读锁的线程(类型同A),此外,为了使线程x读取到Q时知道最近一次的线程是哪一个,Q(A类型)还需要保存其所属的thread的id;同时,对于第一个读锁线程,信息不计入Q,计入专用共享变量B。
    3. 对于前述tip的具体锁逻辑是:如果有其他线程持有写锁,则返回失败;否则,如果等待队列中第一个(指head下一个,这才是等待中的,head是已经获取了lock的)是写锁,即队列中优先级更高的写锁存在,通常来说(见后文M解释)不能加读锁,则进入函数M;否则,将读锁持有数目累加,对于第一个读锁线程,操作B,否则,如果当前线程不是Q记录的线程,则将Q更新(类似指针更新,后续A内容变,Q内容也变)为当前本地A(因为Q始终指向最近一次获取锁的线程),否则不更新Q,同时A中读锁持有数目累加。
    4. 函数M:同上一节函数逻辑相似,相当于全部逻辑再跑一遍,只是如果等待队列第一个是写锁时,不是直接返回失败,还需要特殊处理可能返回成功,即解决interactions between retries and lazily reading hold counts问题(?例如当前线程是重入lock,即已经获取了读锁,再次获取读锁,即使队列中有写锁在等待,也可以获取到?)。

  1. new时,左值要用ReadWriteLock而不是Lock类
  2. ReentrantLock中tryacquire失败,进入队列前会再次tryacquire。而读锁tryacquire失败则直接进入队列


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值