ReentrantReadWriteLock源码分析
文章目录
- 概述
- 一、状态位设计
- 二、读锁
- 三、锁降级机制
- 四、写锁
- 总结
概述
ReentrantReadWriteLock
(读写锁)是对于ReentranLock
(可重入锁)的一种改进,在可重入锁的基础上,进行了读写分离。适用于读多写少
的场景,对于读取
和写入
操作分别加锁。其中读取与读取操作同步
,读取和写入,写入和写入操作互斥
。并且支持写锁降级的机制。
ReentrantReadWriteLock
的体系结构:
- 实现了ReadWriteLock接口,定义了读锁和写锁的模版方法,在ReentrantReadWriteLock分别进行实现(ReentrantReadWriteLock内部实现了两把锁)。
- sync属性,实现了AQS的规范。
- ReadLock和WriteLock都实现了Lock接口,Lock接口作为可重入锁的模版,定义了共有的行为。
实现了读写锁的规范接口
sync属性,是一个静态内部类,继承了AQS,AQS是一种规范,抽象的队列式同步器
MESA管程模型,入口等待队列用于互斥,条件变量等待队列用于同步
ReentrantReadWriteLock中的写锁,实现了Lock接口
ReentrantReadWriteLock中的读锁,同样也实现了Lock接口
Lock接口,规范了锁的实现
ReentrantReadWriteLock 同样支持公平锁和非公平锁的实现
一、状态位设计
ReentrantReadWriteLock
的内部类Sync
,重写了父类AQS的acquireShared方法,定义了读锁的共享、可重入特性,但是AQS的state状态位,只能表示同步状态,不能同时维护读锁、写锁的状态。在读写锁的实现中,对于state状态位,实现了按位切割
的算法:
- 低 16 位(低 2 字节):存储 写锁的
重入次数
。 - 高 16 位(高 2 字节):存储 读锁的
获取次数
。
为什么写锁存储的是重入次数
?写锁通常是互斥的,但有时一个线程可能会多次请求写锁(即重入)所以需要统计的是,某个线程重入了几次写锁
。读锁存储的是获取次数
?读锁是同步的,一般不会某个线程多次请求读锁,所以需要统计的是当前有多少个线程持有读锁
Sync类中,定义了状态位的相关属性,以及获取读,写计数的方法:
// 读锁偏移 16 位,读锁存储在 state 的高 16 位。static final int SHARED_SHIFT = 16;// 读锁的单位值(1 左移 16 位,即 65536)static final int SHARED_UNIT = (1 << SHARED_SHIFT);//最大值 (1 左移 16 位 - 1,即 65535)static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;//用于提取 低 16 位。static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;/** Returns the number of shared holds represented in count *///从 state 变量中提取高 16 位,即 读锁的获取次数。static int sharedCount(int c) { return c >>> SHARED_SHIFT; }/** Returns the number of exclusive holds represented in count */// 按位与 & 操作 提取 低 16 位,即 写锁的重入次数。static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
用一个案例进行说明:
- 假设初始 state = 0(没有读锁和写锁)
state = 0x00000000 (0000 0000 0000 0000 0000 0000 0000 0000)
- 写锁(低 16 位):exclusiveCount(state) = state & 0000 0000 0000 0000 1111 1111 1111 1111 = 0
- 读锁(高 16 位):sharedCount(state) = state >>> 16 = 0
- 假设写锁重入 3 次,此时 state = 3
state = 0x00000003 (0000 0000 0000 0000 0000 0000 0000 0011)
- 写锁(低 16 位):
exclusiveCount(state) = state & 0000 0000 0000 0000 1111 1111 1111 1111
= 00000000 00000000 00000000 00000011 & 0000 0000 0000 0000 1111 1111 1111 1111
= 00000000 00000000 00000000 00000011 (3)
- 读锁(高 16 位):
sharedCount(state) = state >>> 16
= 00000000 00000000 00000000 00000011 >>> 16
= 00000000 00000000 00000000 00000000 (0)
二、读锁
读锁的特点是共享
,也就是读锁和读锁之间不互斥,并且可重入
。那么在源码的层面,是如何定义共享、可重入的?
采用sync的acquireShared
案例代码:
public class Demo1 {private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private static final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();private static final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();public static void main(String[] args) {readLock.lock();System.out.println("第一次获取到读锁");try {readLock.lock();System.out.println("第二次获取到读锁");try {System.out.println("重复获取读锁");}finally {readLock.unlock();System.out.println("第二次获取到读锁被释放");}}finally {readLock.unlock();System.out.println("第一次获取到读锁被释放");}}
}
读锁的加锁操作:
protected final int tryAcquireShared(int unused) {/** Walkthrough:* 1. If write lock held by another thread, fail.* 2. Otherwise, this thread is eligible for* lock wrt state, so ask if it should block* because of queue policy. If not, try* to grant by CASing state and updating count.* Note that step does not check for reentrant* acquires, which is postponed to full version* to avoid having to check hold count in* the more typical non-reentrant case.* 3. If step 2 fails either because thread* apparently not eligible or CAS fails or count* saturated, chain to version with full retry loop.*///获取当前线程Thread current = Thread.currentThread();//获取当前线程的状态标记int c = getState();//已经有了写锁,并且写锁的拥有者不是当前线程if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)//当前有写锁且不是当前线程持有的写锁,返回 -1,表示获取读锁失败。return -1;//得到读锁的获取次数int r = sharedCount(c);//检查当前线程是否需要等待 //确保当前读锁的获取次数 r 小于最大限制 MAX_COUNT//使用 CAS 操作来更新 state。if (!readerShouldBlock() &&r < MAX_COUNT &&compareAndSetState(c, c + SHARED_UNIT)) {//读锁的获取次数为0if (r == 0) {//标记当前线程为第一个获取到的firstReader = current;//记录第一个获取到读锁的线程的获取次数firstReaderHoldCount = 1;//读锁的获取次数不为零,并且当前线程和第一个获取到读锁的线程相同 } else if (firstReader == current) {//第一个获取到读锁的线程的获取次数增加firstReaderHoldCount++;//读锁的获取次数不为零,并且当前线程不是第一个获取到读锁的线程 } else {//从缓存中获取持有计数器HoldCounter rh = cachedHoldCounter;//如果缓存为空或者当前线程的 tid 不匹配if (rh == null || rh.tid != getThreadId(current))//则从 readHolds 中获取新的计数器。cachedHoldCounter = rh = readHolds.get();//如果 rh.count == 0,说明当前线程刚开始持有读锁。else if (rh.count == 0)//更新计数器。readHolds.set(rh);//增加当前线程的读锁计数。rh.count++;}return 1;}//如果前面的 CAS 操作失败,进行 排队等待,直到条件满足为止。return fullTryAcquireShared(current);}
这一段尝试获取读锁的代码,精髓在于使用 CAS 操作来更新 state,并且记录读锁的获取次数,是将第一个线程和后续线程分开计数的。
三、锁降级机制
如果一个线程,先获取到了写锁,然后再去获取读锁,最后释放写锁,写锁能够降级成为读锁,即:一个线程在持有写锁的情况下,将写锁转换为读锁,即允许线程在修改资源之后,在不释放锁的情况下继续读取资源
。(上述的操作,只针对当前线程)
public class Demo2 {private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private static final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();private static final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();public static void main(String[] args) {//先获取到了写锁writeLock.lock();System.out.println(Thread.currentThread().getName() + "获取到写锁");try {//再去获取读锁readLock.lock();System.out.println(Thread.currentThread().getName() + "获取到写锁后,又获取到了读锁");}finally {//最后释放写锁writeLock.unlock();System.out.println(Thread.currentThread().getName() + "释放了写锁");}try{System.out.println("业务代码执行");}finally{readLock.unlock();System.out.println(Thread.currentThread().getName() + "释放了写锁后,释放了读锁");}}
}
锁降级机制,特别适用于:
- 数据修改后再查询:当某个线程对共享资源进行了修改(如数据库更新),并且希望读取最新的数据时,直接降级为读锁可以避免不必要的锁切换。
- 避免频繁锁获取:在某些高并发场景中,多个线程需要频繁读取共享数据,但在一开始线程会进行写操作,降级为读锁可以避免重新获取写锁并减少锁的开销。
为什么要在获取写锁和释放写锁之间,去获取读锁呢?主要是为了保证数据的一致性。避免先释放写锁-再获取读锁
的过程中,其他线程抢先一步获取到了写锁修改了数据。获取读锁-释放写锁
,那么其他线程想要获取写锁修改数据,因为读-写锁之间的互斥,所以其他线程将被阻塞。
读锁尝试获取锁的代码中,下面的片段就是锁降级机制的体现
,即已经有了写锁,并且当前线程是该写锁的持有者,那么可以继续获取读锁,不会返回-1失败:
//已经有了写锁,并且写锁的拥有者不是当前线程if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)//当前有写锁且不是当前线程持有的写锁,返回 -1,表示获取读锁失败。return -1;
四、写锁
在源码的层面,通过AQS的acquire方法,保证不同线程间
写锁-写锁,读锁-写锁之间的互斥性。
案例代码:
public class Demo3 {private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private static final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();private static final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();public static void main(String[] args) {writeLock.lock();System.out.println(Thread.currentThread().getName() + "获取到了写锁");try {writeLock.lock();System.out.println(Thread.currentThread().getName() + "再次获取到了写锁");try {System.out.println("....");}finally {writeLock.unlock();System.out.println(Thread.currentThread().getName() + "再次释放了写锁");}}finally {writeLock.unlock();System.out.println(Thread.currentThread().getName() + "释放了写锁");}}
}
同一线程间写锁间(写锁和读锁间)不互斥
public class Demo3 {private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private static final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();private static final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();public static void main(String[] args) throws InterruptedException {writeLock.lock();System.out.println(Thread.currentThread().getName() + "获取到了写锁");Thread.sleep(3000);try {new Thread(() -> {writeLock.lock();System.out.println(Thread.currentThread().getName() + "再次获取到了写锁");try {System.out.println("....");}finally {writeLock.unlock();System.out.println(Thread.currentThread().getName() + "再次释放了写锁");}}).start();}finally {writeLock.unlock();System.out.println(Thread.currentThread().getName() + "释放了写锁");}}
}
不同线程间的写锁间互斥(写锁和读锁间也互斥)
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();//提取当前状态 c 中的写锁计数int w = exclusiveCount(c);//状态不为0 意味着锁当前处于非空状态if (c != 0) {// (Note: if c != 0 and w == 0 then shared count != 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;}//状态为0//应该被阻塞 或 CAS 状态 + 重入次数 累加失败if (writerShouldBlock() ||!compareAndSetState(c, c + acquires))//返回加锁失败return false;//设置当前线程为锁的持有者setExclusiveOwnerThread(current);return true;}
总结
线程可以获取到读锁的条件:
- 没有其他线程获取到写锁。
- 有写锁,但是是当前线程持有(锁降级机制)
线程可以获取到写锁的条件:
- 没有其他线程获取到读锁。
- 没有其他线程获取到写锁。
对于同一个线程而言:读线程获取读锁后,能够再次获取读锁(不能再获取到写锁,即不支持锁升级)。写线程在获取写锁之后能够再次获取写锁,同时也可以获取读锁。
相关文章:
ReentrantReadWriteLock源码分析
文章目录 概述一、状态位设计二、读锁三、锁降级机制四、写锁总结 概述 ReentrantReadWriteLock(读写锁)是对于ReentranLock(可重入锁)的一种改进,在可重入锁的基础上,进行了读写分离。适用于读多写少的场景…...
ChatGPT-4o和ChatGPT-4o mini的差异点
在人工智能领域,OpenAI再次引领创新潮流,近日正式发布了其最新模型——ChatGPT-4o及其经济实惠的小型版本ChatGPT-4o Mini。这两款模型虽同属于ChatGPT系列,但在性能、应用场景及成本上展现出显著的差异。本文将通过图文并茂的方式࿰…...
小程序设计和开发:什么是竞品分析,如何进行竞品分析
一、竞品分析的定义 竞品分析是指对竞争对手的产品进行深入研究和比较,以了解市场动态、发现自身产品的优势和不足,并为产品的设计、开发和营销策略提供参考依据。在小程序设计和开发中,竞品分析可以帮助开发者了解同类型小程序的功能、用户体…...
计算机网络之计算机网络的分类
计算机网络可以根据不同的角度进行分类,以下是几种常见的分类方式: 1. 按照规模和范围: 局域网(LAN,Local Area Network):覆盖较小范围(例如一个建筑物或校园)…...
什么是门控循环单元?
一、概念 门控循环单元(Gated Recurrent Unit,GRU)是一种改进的循环神经网络(RNN),由Cho等人在2014年提出。GRU是LSTM的简化版本,通过减少门的数量和简化结构,保留了LSTM的长时间依赖…...
ESP32-c3实现获取土壤湿度(ADC模拟量)
1硬件实物图 2引脚定义 3使用说明 4实例代码 // 定义土壤湿度传感器连接的模拟输入引脚 const int soilMoisturePin 2; // 假设连接到GPIO2void setup() {// 初始化串口通信Serial.begin(115200); }void loop() {// 读取土壤湿度传感器的模拟值int sensorValue analogRead…...
获取snmp oid的小方法1(随手记)
snmpwalk遍历设备的mib # snmpwalk -v <SNMP version> -c <community-id> <IP> . snmpwalk -v 2c -c test 192.168.100.201 .根据获取的值,找到某一个想要的值的oid # SNMPv2-MIB::sysName.0 STRING: test1 [rootzabbix01 fonts]# snmpwalk -v…...
【C++篇】哈希表
目录 一,哈希概念 1.1,直接定址法 1.2,哈希冲突 1.3,负载因子 二,哈希函数 2.1,除法散列法 /除留余数法 2.2,乘法散列法 2.3,全域散列法 三,处理哈希冲突 3.1&…...
Nginx开发01:基础配置
一、下载和启动 1.下载、使用命令行启动:Web开发:web服务器-Nginx的基础介绍(含AI文稿)_nginx作为web服务器,可以承担哪些基本任务-CSDN博客 注意:我配置的端口是81 2.测试连接是否正常 访问Welcome to nginx! 如果…...
mysqldump+-binlog增量备份
注意:二进制文件删除必须使用help purge 不可用rm -f 会崩 一、概念 增量备份:仅备份上次备份以后变化的数据 差异备份:仅备份上次完全备份以后变化的数据 完全备份:顾名思义,将数据完全备份 其中,…...
hive:数据导入,数据导出,加载数据到Hive,复制表结构
hive不建议用insert,因为Hive是建立在Hadoop之上的数据仓库工具,主要用于批处理和大数据分析,而不是为OLTP(在线事务处理)操作设计的。INSERT操作会非常慢 数据导入 命令行界面:建一个文件 查询数据>>复制>>粘贴到新…...
【工欲善其事】利用 DeepSeek 实现复杂 Git 操作:从原项目剥离出子版本树并同步到新的代码库中
文章目录 利用 DeepSeek 实现复杂 Git 操作1 背景介绍2 需求描述3 思路分析4 实现过程4.1 第一次需求确认4.2 第二次需求确认4.3 第三次需求确认4.4 V3 模型:中间结果的处理4.5 方案验证,首战告捷 5 总结复盘 利用 DeepSeek 实现复杂 Git 操作 1 背景介绍…...
mac 手工安装OpenSSL 3.4.0
如果你希望继续安装 openssl-3.4.0 而不是降级到 3.1.1,可以尝试以下解决方案。根据你提供的错误信息,问题可能出在测试阶段(make test),我们可以尝试跳过测试或修复测试失败的原因。 --- ### **解决方案:…...
构建一个数据分析Agent:提升分析效率的实践
在上一篇文章中,我们讨论了如何构建一个智能客服Agent。今天,我想分享另一个实际项目:如何构建一个数据分析Agent。这个项目源于我们一个金融客户的真实需求 - 提升数据分析效率,加快决策速度。 从分析师的痛点说起 记得和分析师团队交流时的场景: 小张ÿ…...
【SRC排名】安全应急响应中心SRC上榜记录
2023年 新氧第三 https://security.soyoung.com/top 合合第四 https://security.intsig.com/index.php?m&chall&aindex 2024年 好未来第一 https://src.100tal.com/index.php?m&chall&aindex(官网是总榜,年榜只有海报)…...
截止到2025年2月1日,Linux的Wayland还有哪些问题是需要解决的?
截至2025年2月1日,Wayland需要解决的核心问题可按权重从高到低排序如下: 1. 屏幕共享与远程桌面的完整支持(权重:★★★★★) 问题:企业场景(如 腾讯会议)、开发者远程调试依赖稳定的屏幕共享功能。当前Wayland依赖PipeWire和XWayland,存在权限管理复杂、多显示器选择…...
TCP编程
1.socket函数 int socket(int domain, int type, int protocol); 头文件:include<sys/types.h>,include<sys/socket.h> 参数 int domain AF_INET: IPv4 Internet protocols AF_INET6: IPv6 Internet protocols AF_UNIX, AF_LOCAL : Local…...
Java泛型深度解析(JDK23)
第一章 泛型革命 1.1 类型安全的进化史 前泛型时代的类型转换隐患 代码的血泪史(Java 1.4版示例): List rawList new ArrayList(); rawList.add("Java"); rawList.add(Integer.valueOf(42)); // 编译通过// 灾难在运行时爆发…...
【JavaEE进阶】图书管理系统 - 壹
目录 🌲序言 🌴前端代码的引入 🎋约定前后端交互接口 🚩接口定义 🍃后端服务器代码实现 🚩登录接口 🚩图书列表接口 🎄前端代码实现 🚩登录页面 🚩…...
搜索旋转排序数组(二分查找)
测试链接:https://leetcode.cn/problems/search-in-rotated-sorted-array/https://leetcode.cn/problems/search-in-rotated-sorted-array/https://leetcode.cn/problems/search-in-rotated-sorted-array/ 问题描述 假设我们有一个旋转排序的数组,这个…...
STM32 TIM定时器配置
TIM简介 TIM(Timer)定时器 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时 不仅具备基本的定时中断功能ÿ…...
AI开发之 ——Anaconda 介绍
Anaconda 是什么? 在这里插入图片描述 一句话:Anaconda 是Python 库和环境便捷管理的平台。 Anaconda 是数据科学和 AI 领域的工具,通过集成常用库和工具,简化了环境管理和包安装,特别适合初学者和需要快速上手的开…...
Uber损失(Huber Loss):从均方误差到绝对误差的完美过渡
前言 在机器学习的世界里,损失函数就像是你在迷宫中的导航系统,它决定了你到底能否顺利找到出口,而出口的大小就代表着模型的表现。而在这么多的“导航系统”中,Huber损失(你可以叫它“Uber损失”,我觉得这名字挺有意思的,能不能打车到一个更好的模型呢?)凭借其独特的…...
【Arxiv 大模型最新进展】TOOLGEN:探索Agent工具调用新范式
【Arxiv 大模型最新进展】TOOLGEN:探索Agent工具调用新范式 文章目录 【Arxiv 大模型最新进展】TOOLGEN:探索Agent工具调用新范式研究框图方法详解 作者:Renxi Wang, Xudong Han 等 单位:LibrAI, Mohamed bin Zayed University o…...
41【文件名的编码规则】
我们在学习的过程中,写出数据或读取数据时需要考虑编码类型 火山采用:UTF-16 易语言采用:GBK php采用:UTF-8 那么我们写出的文件名应该是何种编码的?比如火山程序向本地写出一个“测试.txt”,理论上这个“测…...
Linux命令入门
Linux命令入门 ls命令 ls命令的作用是列出目录下的内容,语法细节如下: 1s[-a -l -h] [Linux路径] -a -l -h是可选的选项 Linux路径是此命令可选的参数 当不使用选项和参数,直接使用ls命令本体,表示:以平铺形式,列出当前工作目录下的内容 ls命令的选项 -a -a选项&a…...
如何用函数去计算x年x月x日是(C#)
如何用函数去计算x年x月x日是? 由于现在人工智能的普及,我们往往会用计算机去算,或者去记录事情 1.计算某一年某一个月有多少天 2.计算某年某月某日是周几 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threadin…...
29.Word:公司本财年的年度报告【13】
目录 NO1.2.3.4 NO5.6.7 NO8.9.10 NO1.2.3.4 另存为F12:考生文件夹:Word.docx选中绿色标记的标题文本→样式对话框→单击右键→点击样式对话框→单击右键→修改→所有脚本→颜色/字体/名称→边框:0.5磅、黑色、单线条:点…...
Flutter常用Widget小部件
小部件Widget是一个类,按照继承方式,分为无状态的StatelessWidget和有状态的StatefulWidget。 这里先创建一个简单的无状态的Text小部件。 Text文本Widget 文件:lib/app/app.dart。 import package:flutter/material.dart;class App exte…...
高可用 Keepalived 服务部署流程
一、配置文件 vim /etc/keepalived/keepalived.confGLOBAL CONFIGURATION --- 全局配置部分VRRPD CONFIGURATION --- VRRP协议配置部分LVS CONFIGURATION --- LVS服务管理配置部分[rootlb01 ~]# cat /etc/keepalived/keepalived.…...
网站结构优化:加速搜索引擎收录的关键
本文来自:百万收录网 原文链接:https://www.baiwanshoulu.com/9.html 网站结构优化对于加速搜索引擎收录至关重要。以下是一些关键策略,旨在通过优化网站结构来提高搜索引擎的抓取效率和收录速度: 一、合理规划网站架构 采用扁…...
【深度学习】softmax回归的从零开始实现
softmax回归的从零开始实现 (就像我们从零开始实现线性回归一样,)我们认为softmax回归也是重要的基础,因此(应该知道实现softmax回归的细节)。 本节我们将使用Fashion-MNIST数据集,并设置数据迭代器的批量大小为256。 import torch from IP…...
AMS仿真方法
1. 准备好verilog文件。并且准备一份.vc文件,将所有的verilog file的路径全部写在里面。 2. 将verilog顶层导入到virtuoso中: 注意.v只要引入顶层即可。不需要全部引入。实际上顶层里面只要包含端口即可,即便是空的也没事。 引入时会报warni…...
多模态论文笔记——ViViT
大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细解读多模态论文《ViViT: A Video Vision Transformer》,2021由google 提出用于视频处理的视觉 Transformer 模型,在视频多模态领域有…...
Flink2支持提交StreamGraph到Flink集群
最近研究Flink源码的时候,发现Flink已经支持提交StreamGraph到集群了,替换掉了原来的提交JobGraph。 新增ExecutionPlan接口,将JobGraph和StreamGraph作为实现。 Flink集群Dispatcher也进行了修改,从JobGraph改成了接口Executio…...
机器学习优化算法:从梯度下降到Adam及其变种
机器学习优化算法:从梯度下降到Adam及其变种 引言 最近deepseek的爆火已然说明,在机器学习领域,优化算法是模型训练的核心驱动力。无论是简单的线性回归还是复杂的深度神经网络,优化算法的选择直接影响模型的收敛速度、泛化性能…...
2024具身智能模型汇总:从训练数据、动作预测、训练方法到Robotics VLM、VLA
前言 本文一开始是属于此文《GRAPE——RLAIF微调VLA模型:通过偏好对齐提升机器人策略的泛化能力》的前言内容之一(该文发布于23年12月底),但考虑到其重要性,加之那么大一张表格 看下来 阅读体验较差,故抽出取来独立成文且拆分之 …...
基于Spring Security 6的OAuth2 系列之七 - 授权服务器--自定义数据库客户端信息
之所以想写这一系列,是因为之前工作过程中使用Spring Security OAuth2搭建了网关和授权服务器,但当时基于spring-boot 2.3.x,其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0,结果一看Spring Security也升级…...
当WebGIS遇到智慧文旅-以长沙市不绕路旅游攻略为例
目录 前言 一、旅游数据组织 1、旅游景点信息 2、路线时间推荐 二、WebGIS可视化实现 1、态势标绘实现 2、相关位置展示 三、成果展示 1、第一天旅游路线 2、第二天旅游路线 3、第三天旅游路线 4、交通、订票、住宿指南 四、总结 前言 随着信息技术的飞速发展&…...
浅析CDN安全策略防范
CDN(内容分发网络)信息安全策略是保障内容分发网络在提供高效服务的同时,确保数据传输安全、防止恶意攻击和保护用户隐私的重要手段。以下从多个方面详细介绍CDN的信息安全策略: 1. 数据加密 数据加密是CDN信息安全策略的核心之…...
Python安居客二手小区数据爬取(2025年)
目录 2025年安居客二手小区数据爬取观察目标网页观察详情页数据准备工作:安装装备就像打游戏代码详解:每行代码都是你的小兵完整代码大放送爬取结果 2025年安居客二手小区数据爬取 这段时间需要爬取安居客二手小区数据,看了一下相关教程基本…...
Python爬虫获取custom-1688自定义API操作接口
一、引言 在电子商务领域,1688作为国内领先的B2B平台,提供了丰富的API接口,允许开发者获取商品信息、店铺信息等。其中,custom接口允许开发者进行自定义操作,获取特定的数据。本文将详细介绍如何使用Python调用1688的…...
CAPL与外部接口
CAPL与外部接口 目录 CAPL与外部接口1. 引言2. CAPL与C/C++交互2.1 CAPL与C/C++交互简介2.2 CAPL与C/C++交互实现3. CAPL与Python交互3.1 CAPL与Python交互简介3.2 CAPL与Python交互实现4. CAPL与MATLAB交互4.1 CAPL与MATLAB交互简介4.2 CAPL与MATLAB交互实现5. 案例说明5.1 案…...
解析与使用 Apache HttpClient 进行网络请求和数据抓取
目录 1. 什么是 HttpClient? 2. 基本使用 3. 使用 HttpClient 爬取腾讯天气的数据 4. 爬取拉勾招聘网站的职位信息 5. 总结 前言 Apache HttpClient 是 Apache 提供的一个用于处理 HTTP 请求和响应的工具类库。它提供了一种便捷、功能强大的方式来发送 HTTP 请…...
【go语言】结构体
一、type 关键字的用法 在 go 语言中,type 关键字用于定义新的类型,他可以用来定义基础类型、结构体类型、接口类型、函数类型等。通过 type 关键字,我们可以为现有类型创建新的类型别名或者自定义新的类型。 1.1 类型别名 使用 type 可以为…...
Kotlin 委托详解
Kotlin 委托详解 引言 Kotlin 作为一种现代化的编程语言,在 Android 开发等领域得到了广泛的应用。在 Kotlin 中,委托(Delegation)是一种强大的特性,它可以让我们以更简洁的方式实现代码的复用和扩展。本文将详细解析…...
用QT做一个网络调试助手
文章目录 前言一、TCP网络调试助手介绍1. 项目概述2. 开发流程3. TCP服务器的关键流程4. TCP客户端的关键流程 二、实现UI界面1. 服务器界面2. 客户端界面 三、实现代码框架1. 服务器代码1.1 初始化服务器地址1.2 开始监听1.3 与客户端连接1.4 接收客户端信息1.5 判断客户端状态…...
Qt 5.14.2 学习记录 —— 이십이 QSS
文章目录 1、概念2、基本语法3、给控件应用QSS设置4、选择器1、子控件选择器2、伪类选择器 5、样式属性box model 6、实例7、登录界面 1、概念 参考了CSS,都是对界面的样式进行设置,不过功能不如CSS强大。 可通过QSS设置样式,也可通过C代码…...
HTML 符号详解
HTML 符号详解 引言 HTML(超文本标记语言)符号是HTML文档中用来表示特殊字符的标记。这些符号在日常网页设计和开发中扮演着重要角色,特别是在需要显示版权、商标、货币符号等特殊字符时。本文将详细介绍HTML符号的用法、类型以及如何在HTML文档中插入这些符号。 HTML符号…...
第十二章 I 开头的术语
文章目录 第十二章 I 开头的术语以 I 开头的术语被识别 (identified by)识别关系 (identifying relationship)身份 (identity)idkey隐式全局引用 (implicit global reference)隐含命名空间 (implied namespace)包含文件 (include file)传入锁 (incoming lock) 索引 (index)索引…...