ReentrantReadWriterLock

读写锁类图(截图来源https://blog.csdn.net/wangbo199308/article/details/108688148)

state的设计

读写锁将变量state切分成两个部分,高16位表示读,低16位表示写

源码中将4字节(32位)的int数据类型state,通过SHARED_SHIFT(16)划分读和写;

每次读锁增加的单元,SHARED_UNIT = (1 << SHARED_SHIFT) 也即0x00010000,即每次读锁增加从17位开始加1

读写锁最大数量:MAX_COUNT = (1 << SHARED_SHIFT) - 1,16位最大值

写锁的掩码:EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1, 即求写锁数量,将state和此掩码做与运算,将高16位抹去

计算读锁数量逻辑:c >>> SHARED_SHIFT,取高16位

计算写锁数量逻辑:c & EXCLUSIVE_MASK,将state和此掩码做与运算,将高16位抹去

public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
abstract static class Sync extends AbstractQueuedSynchronizer {
//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; }
}
}
读锁

读锁上锁的调用链:ReentrantReadWriteLock$ReadLock#lock() -->AbstractQueuedSynchronizer#acquireShared() -->ReentrantReadWriteLock$Sync#tryAcquireShared()

当前写锁数量为0或独占锁持有者就是当前线程才进行读锁逻辑

读锁数量通过CAS加1

之后逻辑是将读锁线程放入ThreadLocal中,记录各自锁数量

public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
public static class ReadLock implements Lock, java.io.Serializable {
public void lock() {
sync.acquireShared(1);
}
}
}
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
}
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
abstract static class Sync extends AbstractQueuedSynchronizer {
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 同时满足写锁数量不为0,且独占锁不是当前线程,走doAcquireShared逻辑
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 取高16位读锁数量
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// ThreadLocal存放锁信息
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
}
}

在读锁获取锁过程,写锁不为0且占有写锁的不是当前线程,返回-1,走同步器doAcquireShared方法,等待写锁释放;

前置节点是head节点时,尝试获取共享锁

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) {
//前置节点是head节点时,尝试获取共享锁
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
写锁
  1. 读锁不为0,但写锁为0,获取锁失败;读锁不为0,写锁也不为0,但独占锁不是当前线程,获取锁失败
  2. 如果锁数量已到最大,获取失败
  3. 否则获取写锁,更新state
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
abstract static class Sync extends AbstractQueuedSynchronizer {
protected final boolean tryAcquire(int acquires) { 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)
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;
}
}
}
共享锁和独占锁

读锁是共享锁,当线程1获得读锁时,并不会排斥线程2去获取读锁,而是在ThreadLocal中保存每个锁数量

    abstract static class Sync extends AbstractQueuedSynchronizer {
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
} static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
}

写锁是独占锁,会调用同步器AbstractQueuedSynchronizer#acquire()方法,默认加入队列的node模式是独占模式

    public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
锁降级

锁降级就是从写锁降级成为读锁。在当前线程拥有写锁的情况下,再次获取到读锁,随后释放写锁的过程就是锁降级

锁降级示例:

public void processData() {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
readLock.lock();
if(!update) {
//必须先释放读锁
readLock.unlock();
// 锁降级从写锁获取到开始
writeLock.lock();
try{
if(!update) {
update = true;
}
// 可以获取到读锁,getExclusiveOwnerThread() == current
readLock.lock();
} finally {
writeLock.unlock();
}
//锁降级完成,写锁降级为读锁
}
try{
// 使用数据的流程
} finally {
readLock.unlock();
}
}

可降级的源码仍是在读锁tryAcquireShared方法中,getExclusiveOwnerThread() == current,也即当前独占锁owner就是当前线程,可进行读锁逻辑。

protected final int tryAcquireShared(int unused) {
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
}

参考:《Java并发编程的艺术》

ReentrantReadWriterLock源码(state设计、读写锁、共享锁、独占锁及锁降级)的更多相关文章

  1. 源码分析— java读写锁ReentrantReadWriteLock

    前言 今天看Jraft的时候发现了很多地方都用到了读写锁,所以心血来潮想要分析以下读写锁是怎么实现的. 先上一个doc里面的例子: class CachedData { Object data; vo ...

  2. jQuery2.x源码解析(设计篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...

  3. [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作

    [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作 目录 [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作 0x00 摘要 0x01 业务领域 1.1 SOFARegis ...

  4. [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用

    [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 目录 [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 0x00 摘要 0x01 业务领域 1.1 应用场景 0x02 定 ...

  5. [从源码学设计]蚂蚁金服SOFARegistry之续约和驱逐

    [从源码学设计]蚂蚁金服SOFARegistry之续约和驱逐 目录 [从源码学设计]蚂蚁金服SOFARegistry之续约和驱逐 0x00 摘要 0x01 业务范畴 1.1 失效剔除 1.2 服务续约 ...

  6. [从源码学设计]蚂蚁金服SOFARegistry之程序基本架构

    [从源码学设计]蚂蚁金服SOFARegistry之程序基本架构 0x00 摘要 之前我们通过三篇文章初步分析了 MetaServer 的基本架构,MetaServer 这三篇文章为我们接下来的工作做了 ...

  7. [从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理

    [从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理 目录 [从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理 0x00 摘要 0x01 业务领域 1.1 应用场景 0x ...

  8. [从源码学设计]蚂蚁金服SOFARegistry之消息总线

    [从源码学设计]蚂蚁金服SOFARegistry之消息总线 目录 [从源码学设计]蚂蚁金服SOFARegistry之消息总线 0x00 摘要 0x01 相关概念 1.1 事件驱动模型 1.1.1 概念 ...

  9. [从源码学设计]蚂蚁金服SOFARegistry之消息总线异步处理

    [从源码学设计]蚂蚁金服SOFARegistry之消息总线异步处理 目录 [从源码学设计]蚂蚁金服SOFARegistry之消息总线异步处理 0x00 摘要 0x01 为何分离 0x02 业务领域 2 ...

随机推荐

  1. sqlilab less1-less10

    less-1 参数被单引号包裹,加单引号,闭合后绕过 less-2 参数没有被包裹,直接带入查询,不需要闭合 less-3 参数被 ('$id') 包裹,需要将他闭合 less-4 参数被小括号和双引 ...

  2. ctf-工具-binwalk

    binwalk在玩杂项时是个不可缺的工具.1.最简单的,在玩隐写时,首先可以用它来找到其中的字符串例如:在铁人三项,东北赛区个人赛中,有一道题它直接给了一个文件,没有后缀,不知道是什么文件先binwa ...

  3. 企业级工作流解决方案(十五)--集成Abp和ng-alain--Abp其他改造

    配置功能增强 Abp定义了各种配置接口,但是没有定义这些配置数据从哪里来,但是管理配置数据对于一个应用程序来说,是必不可少的一件事情. .net的配置数据管理,一般放在Web.config文件或者Ap ...

  4. 面试阿里,首先要掌握的 Java 泛型,帮你一次性搞懂!

    引言 泛型是Java中一个非常重要的知识点,在Java集合类框架中泛型被广泛应用.本文我们将从零开始来看一下Java泛型的设计,将会涉及到通配符处理,以及让人苦恼的类型擦除. 泛型基础 泛型类 我们首 ...

  5. 精尽MyBatis源码分析 - SQL执行过程(二)之 StatementHandler

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  6. 【ACwing 98】分形之城——分形

    (题面来自ACwing) 城市的规划在城市建设中是个大问题. 不幸的是,很多城市在开始建设的时候并没有很好的规划,城市规模扩大之后规划不合理的问题就开始显现. 而这座名为 Fractal 的城市设想了 ...

  7. Leetcode 周赛#202 题解

    本周的周赛题目质量不是很高,因此只给出最后两题题解(懒). 1552 两球之间的磁力 #二分答案 题目链接 题意 有n个空篮子,第i个篮子位置为position[i],现希望将m个球放到这些空篮子,使 ...

  8. 【Makefile】5-Makefile变量的基础

    目录 前言 概念 Chapter 5:变量的基础 5.1 变量的基础 * 空格的定义 ** 一些赋值 一些特殊的符号 5.2 变量中的变量 * 5.3 变量高级用法 变量值替换 把变量的值再当成变量 ...

  9. Java集合【8】-- ArrayList源码分析

    目录 1. ArrayList 1.1 ArrayList特点介绍 1.2 实现的接口和继承的类 2. 成员变量 3. 构造方法 4. 常用增删改查方法 添加元素 查询元素 更新元素 删除元素 5.自 ...

  10. 知识点:C语言进阶提高篇,自定义数据类型:枚举

    一.枚举的概念 枚举是C语言中的一种基本数据类型,并不是构造类型,它可以用于声明一组常数.当一个变量有几个固定的可能取值时,可以将这个变量定义为枚举类型.比如,你可以用一个枚举类型的变量来表示季节,因 ...