- 博客(48)
- 收藏
- 关注
原创 [Java 并发编程] Synchronized 锁升级
下一步是 CAS,这里的 CAS 涉及到这几个操作数:对象 MarkWord 的内存地址,期望的处于无锁状态的 MarkWord,指向自身锁记录的指针。如果操作成功,线程要将锁记录中的 owner 指针指向锁对象,这样在释放锁的时候才能找得到锁对象,接下来线程就可以访问共享资源了。这是因为,线程在退出偏向锁时,不会撤销偏向锁,它的线程 ID 依然留在 MarkWord 中,不会置为 NULL,除非这个线程挂了,JVM 才会重新偏向。偏向锁状态下,是没有竞争的,线程加锁过程如下:线程 ID 为自己的 ID?
2026-01-10 19:16:42
596
原创 [Java 并发编程] ThreadLocal 原理
ThreadLocal 本质上还是空间换时间的思想,每个线程修改自己的副本,从而无锁并发执行。
2026-01-07 14:40:10
719
原创 [算法基础] FooldFill(DFS、BFS)
这道题要找所有被围绕的 O 区域,不太好找,因此我们可以先找那些不被围绕的 O 区域,这些区域是好找的,因为它们一定至少有一个坐标是位于矩阵边缘的。现在我们仅需探测矩阵的边缘即可,碰到 O 就 DFS 进去。
2026-01-04 10:16:10
233
原创 [算法基础] BFS
注意如何维护二叉树每一层的下标(左孩子下标为父节点 * 2,右孩子下标为父节点 * 2 + 1)。也可以将 (treenode, 下标) 定义为内部类。注意 Deque 的使用。
2026-01-04 10:15:26
181
原创 [算法基础] DFS
想要传递给下层调用栈的信息 / 想要从上层调用栈获取的信息,需要存在于递归方法参数中,或将其定义为全局变量。如果定义为全局变量则必须手动回溯,如果定义为方法参数且为基本数据类型或 String 则调用栈会帮我们自动回溯,引用类型还是要手动回溯(否则必须在方法中创建副本)。优先定义全局变量。递归方法的返回值是想要传递给上层调用栈的信息。先想清楚重复的子问题是什么。
2026-01-04 10:14:14
691
原创 [Redis] 分布式缓存与分布式锁
1. 避免并发写问题:在更新缓存策略下,如果两个写操作并发执行,由于网络延迟等原因,它们更新缓存的操作顺序可能与更新数据库的顺序不一致,从而导致缓存中是旧数据。比如 A 更新数据库,然后 B 更新数据库,然后 B 更新缓存,最后 A 更新缓存。此时,数据库中的数据是 A 更新后 B 又更新后的数据,而缓存中的数据只是 A 更新后的数据,因为 B 的更新被覆盖了。如果是删除缓存,则最终一定能保证缓存是被清空的。下一个读请求会因为缓存未命中而从数据库读取最新的值并重新填充缓存,这就保证了最终一致性。
2025-12-23 19:29:13
959
原创 [MySQL] 设计范式与 E-R 图绘制
范式是数据库设计的一系列指导原则,旨在通过减少数据冗余和提高数据一致性来组织数据,从而构建出对数据关系约束严格的数据库,以保证数据的准确性。但数据冗余越少,也意味着查询效率可能降低,因为多表联查的概率大大增加,因此现在的数据库设计只满足到第三范式的程度即可。为了更高的查询效率,我们也可以适当增加冗余字段,也就是反范式设计。第一范式要求数据库表中的所有字段都是原子性的,即不可再分。这是因为,如果一列包含多个值,对该列的查询、更新和删除操作会变得非常复杂和低效,并难以对其进行约束。
2025-12-21 10:45:39
663
原创 [MySQL] 服务器架构
词法分析阶段,我们的 SQL 被拆分成一个一个的 Token,这些 Token 被识别为关键字、标识符、运算符、字面量等。开始执行的时候,要先判断一下你对这个表有没有执行查询的权限。打开表的时候,执行器会根据表的存储引擎定义,去调用这个引擎提供的接口,再下面就进入存储引擎层了。语法分析阶段,关心这些 Token 的排列是否符合 SQL 语法规则,此时误写的 elect 就会报错,因为查询必须是关键字开头,而不是标识符开头。存储引擎负责数据的存储与读取,使用插件式的架构,不同的存储引擎共用一个服务器层。
2025-12-21 10:39:02
893
原创 [MySQL] 初识 MySQL 与 SQL 基础
MySQL 是一个客户端服务器结构的程序。MySQL 的服务器是主体,提供数据管理服务,硬盘存储。MySQL 是一个关系型数据库。关系二字首先特指一种由行和列组成的二维表格,这样的表格在 MySQL 中称为 table。table 中的每一行称为一条记录(record),表示一个具体的实体;每一列称为一个字段(field),描述一个实体某项具体的特征,每一列都定义了严格的数据类型。
2025-12-21 10:35:46
1085
原创 [Java 并发编程] 线程池
我们之所以引入线程,是因为进程的创建和销毁过于重量,而线程可以共享更多内存资源,因此成为显著提高效率的手段。但线程也是 OS 分配的,也涉及用户态和内核态的切换,也是一种很有限的资源,其创建和销毁的开销虽然比进程小,但也不可忽视。 如果想在线程的基础上,进一步提高并发编程的效率,目前有两种主流解决方案: 协程:这是纯用户态的轻量级线程,由程序自己控制调度,不涉及操作系统内核切换。在 Java 中被称为 VirtualThread。 线程池:使用池化技术提高资源利用率。
2025-12-12 11:54:36
1035
原创 [Java 并发编程] Thread 类
在 Java 中,我们不能随意地中断一个线程,因为我们不清楚这个线程的运行状态,它可能持有锁,如果强行中断它可能会导致锁不能被释放的问题。或者可能正在操作数据库,强行中断可能出现数据不一致的情况。因此,Interrupt方法实际上是为目标线程设置一个中断状态,而不是真的中断其运行,目标线程的具体退出时机由其自己决定。 如果目标线程处于运行状态,则不会受任何影响,只是状态被标记为中断,目标线程需调用来监听这个状态,从而响应中断信号。 如果目标线程处于阻塞状态(wait()join()
2025-12-12 08:52:09
391
原创 [MySQL] 事务的隔离性与 MVCC
MVCC 是用于保护一般SELECT语句进行 “快照读” 的机制,根本目的就是使这种 “仅读事务” 能够在不对表进行锁控制的情况下安全地与其他事务并发执行。 对于 “读写事务” 和 “读写事务” 的并发来说,MVCC 就无能为力了,还是要进行一定的锁控制。
2025-12-05 20:50:07
1068
原创 [Java 并发编程] 源码层面解读 ObjectMonitor
Java 对象在堆内存中的存储布局可以分为三部分:对象头、实例数据、对齐填充。其中,对象头包含了对象在运行时所需的一些元数据,它主要由两部分组成: Mark Word:记录对象自身的运行时数据。 类型指针:它指向元空间中该对象对应的类元数据(Klass 结构体),JVM 通过这个指针来判断该对象是哪个类的实例。 在这里我们需要重点关注的是 Mark Word,理解它是理解 synchronized、锁状态升级等概念的关键。
2025-11-28 13:14:22
1008
原创 [Java 并发编程] 在 Java 中创建线程
事实上,Java 有且仅有一种方式可以真正创建出线程,那就是通过调用 Thread 类中的start()方法。因为只有这个方法会去真正调用 JVM 本地方法,进而进行操作系统创建线程的系统调用,并让 Java 线程与操作系统线程产生映射关系,最后将实现了 Runnable 接口的任务传递给线程。只要线程获得了 CPU 时间片,就进入 run() 方法执行具体的代码逻辑。 所以,仅仅有 start 方法我们就可以创建一个线程了,如下面的代码所示。
2025-11-24 21:19:31
1147
原创 [MySQL] 备份与恢复
MySQL 能恢复到半个月内任意一秒的状态,这是怎么做到的?它依赖:全量备份(状态快照) + 增量备份(Binlog 重放)。
2025-11-19 21:44:23
1458
2
原创 [MySQL] 数据目录与日志开篇
如果配置使其记录不使用索引的查询,最好使用 log_throttle_queries_not_using_indexes 控制未使用索引的查询的记录频率,否则日志将急速膨胀。2. 写入 TABLE,虽然便于查询和分析(使用客户端与 SQL 语句来筛选和查看日志),但性能开销较大,因为每次日志记录都是一次数据库写入操作,通常仅用于临时调试。3. 即使已经配置开启了通用查询日志和慢查询日志,如果 log_output 设置为 NONE,那么也不会有任何日志被记录。4. 通用查询日志和表输出会带来巨大性能开销。
2025-11-15 21:28:24
435
原创 [MySQL] 深度解析 Redo Log 与灾后恢复
Redo Log 也称重做日志、事务日志,是 InnoDB 独有的日志形式,它的核心作用就是保证事务的持久性。只要事务提交成功,对数据库的修改就永久保存下来了,即使发生系统崩溃,数据也不会丢失。想要理解 Redo Log 的作用,首先需要明确,数据库在修改数据时,并不是每次操作都直接写入磁盘数据文件。如果每次更新都去磁盘 IO,这个效率是非常低的,因为找到一个数据页进行修改是随机磁盘 IO。为了解决这个问题,InnoDB 使用了缓冲池(Buffer Pool)。
2025-11-15 21:19:23
1733
1
原创 [MySQL] 高性能优化
观察单次查询性能: 使用 MySQL 提供的客户端程序 mysqlslap 进行压力测试,配置如下: 使用执行计划查看 SQL 语句的执行情况,这是判断如何优化 SQL 和索引的最重要工具。 EXPLAIN 返回的结果中,最重要的字段是 type,其次是 key、rows、extra。 key 表示实际使用的索引,rows 表示预计需要扫描的行数。type 和 extra 会在下面重点介绍: 这个字段描述了 MySQL 是如何找到目
2025-11-06 15:45:10
1023
原创 [Redis] 集群
之前介绍的 Redis 主从结构,已经提高了系统的可用性,但是实际上全部业务数据依然存储在主从节点上。也就是说,主从结构并不是 Redis 服务的横向扩展,只是缓解了读压力,缓解不了写压力。如果业务继续扩展,需要缓存的数据进一步增加,写压力进一步增加,一个主节点就疲于应付了。此时就会有两种策略:要么为主从节点上更高的硬件配置,要么部署 Redis 切片集群。所谓 Redis 切片集群就是部署多台 Master,同时对外提供读写服务,下文主要介绍的就是这种策略。
2025-10-28 18:54:31
925
原创 [Redis] 哨兵
Redis 提供的哨兵用于在主从复制中,自动地实现故障转移,此时无需开发者手动进行主从切换。展开来说,Sentinel 负责监控所有 Redis 节点的工作状态,如果它发现 Master 出现故障(通常由多个 Sentinel 节点协同判断)则自动化地将一个 Slave 提升为 Master,并让其他 Slave 连接该新 Master。故障转移后,Sentinal 还会将新的主从拓扑结构告知客户端。整个过程不需要人工介入。
2025-10-19 16:30:08
990
原创 [Redis] 主从数据同步
对于单主机部署 Redis 来说,存在单点问题。若该主机宕机,则会导致全部的缓存请求在瞬间打到数据库,从而很有可能将数据库也搞挂。为此,我们可以在多个主机上部署 Redis,一个作为主节点,其他的作为从节点。主节点只负责写请求,由一个或多个从节点负责读请求,从节点定期向主节点同步数据,也就是所谓读写分离的思想。这样有两个好处,第一,提高系统并发量,因为硬件资源变多了;第二,提高 Redis 服务的可用性,某个从节点宕机不会瞬间对系统产生太大影响,主节点宕机也可以使用主从复制来快速实现故障转移。
2025-10-18 09:49:43
809
原创 [Redis] 持久化与事务
1. RESP 协议RESP (REdis Serialization Protocol) 是 Redis 自定义的应用层协议。所谓自定义应用层协议,就是自定义一套解析和构造字符串的规则。Redis 将其完全公开并开源,任何开发者都可以根据这份详细的文档,用任何编程语言实现一个能与 Redis 服务器对话的客户端。例如,使用 Java 实现的 Redis 客户端 Jedis。
2025-10-16 10:37:23
760
原创 [网络编程] 网络层 IP
以 IPv4 和 IPv6 为主。IP 协议的报头是可变长的。IP 协议可以切换工作状态,具体有,最小延时、最大吞吐量、最高可靠性和最小成本。四者选其一。IP 数据报报头加载荷的长度。长度为 16 位,大约 64 KB。IP 协议内置了拆包和组包功能。三者共同用于维护 IP 协议拆包和组包的逻辑。标识为被拆散的包做相同的标记,用于组包。标志位用于提示当前是否拆包,以及当前包是否为最后一个。偏移描述了数据的先后顺序。IP 数据报能够在网络上传输的最大时间。
2025-09-29 16:58:36
887
原创 [算法基础] String、Hash、Stack、Heap
注意:new ArrayList(hash.values()) 可以简洁地将 Hash 的 value 集合转换为一个独立的 ArrayList。但值得注意的是,这个操作创建的是 "浅拷贝",List<String> 是共享的。技巧:使用数组模拟栈。技巧:将每个字符串排序后作为哈希表的键,所有排序后相同的字符串(即字母异位词)会被分到同一组。使用到的内置方法:startsWith()、isEmpty()、substring()使用到的内置方法:Arrays.sort()、computeIfAbsent()
2025-09-20 11:37:02
330
原创 [算法基础] 动态规划 · 下篇
子序列问题意味着我们需要找到数组中符合条件的,但不一定连续的一段。dp[i] 表示以这个位置为结尾的最长递增子序列。子序列问题与子数组问题的不同点在于,子序列是不连续的,因此我们不只要看该元素前一个元素的 dp 值,而是要遍历检查它前面的所有元素。这道题可以将时间优化到 O(n log(n)),但需要使用与上面代码不同的思路。常规的 dp 通过比较每个元素与前面所有元素来更新最长长度,效率较低。
2025-09-19 17:41:21
1168
原创 [Redis] 初识 Redis 与基础数据结构
1. 纯内存访问。这是 Redis 快的基础。2. 非阻塞 IO。3. 单线程避免线程竞争开销,也无需考虑线程安全问题。Redis 的核心业务基本都是对内存数据的简单操作,不会特别消耗 CPU,单线程足以应对。4. Redis 并没有关系型数据库那样复杂的功能支持。比如关系型数据库中的各种约束。
2025-09-05 15:47:38
885
原创 [算法基础] 链表
1. 引入虚拟头结点。2. 多利用指针保存节点,此时不用纠结连接顺序会使链表断开的问题。3. 头插常用于使链表逆序。所谓的头插,也要在虚拟头结点之后插入。因此更准确地说,是每次都在虚拟头结点后尾插,以达到逆序链表的效果。4. 尝试使用快慢指针。5. 加深理解引用传递:对于 Node cur = head;这行代码,意为使得 cur 和 head 指向同一个内存地址。因此,只有修改 cur 或 head 的内部结构而非其本身时,影响会传递给对方。
2025-09-01 21:13:32
824
原创 [网络编程] 传输层 UDP、TCP
客户端发送 ACK 后即进入 ESTABLISHED 状态。正常情况下,ESTABLISHED 状态的客户端不应该收到 SYN=1 的包,因为 SYN 标志仅用于连接建立阶段。如果收到 SYN=1 的包,客户端会判断这是服务器对丢失 ACK 的重传,则忽略 SYN 标志并重发 ACK。
2025-08-15 08:42:50
779
原创 [Spring] Spring AOP 的使用与原理
AOP 全称Aspect Oriented Programming,意为面向切面编程。切面指一类特定的,可以被统一处理的问题,因此 AOP 可以理解为面向特定问题编程。Spring 提供的拦截器就是对 AOP 思想的一种实现。除了解决登录校验的问题,我们还可能遇到许多其他的,需要被统一处理的问题,这些问题就需要使用 Spring 对 AOP 的支持来实现。因此,简单来说 AOP 就是一种将一类问题集中处理的思想。
2025-08-03 12:01:21
1185
1
原创 [算法基础] 动态规划 · 上篇
用于快速计算数组中一段区间的和。准备前缀和数组:前缀和数组 i 下标位置的值表示原数组 0 ~ i 位置值的和。使用前缀和数组:arr[a] + arr[a+1] + ...... + arr[b] 即为 dp[b] - dp[a - 1]
2025-07-25 13:35:16
1106
原创 [算法基础] 分治(快排、归并)
这道题类似于交易的逆序对那道题,唯一的区别就是,这道题的命中条件是 nums[i] > 2 * nums[j],不再是 nums[i] > nums[j] 了。归并排序的思想类似快速排序,快速排序是先将数组大致排序,再递归,类似二叉树的前序遍历,归并排序是先递归,递归到最后再排序,层层返回排好序的结果,类似二叉树的后序遍历。但是归并排序时,每层调用栈拿到的数组都是改变过位置的,这就需要我们手动记录角标的变化。只要我们始终明确,要统计的那个数最初所在的下标,就可以做到对号入座,分别记录了。
2025-07-24 14:08:35
406
原创 [算法基础] 位运算
异或,同 0 异 1(相加不进位) 现给一个整数 n,其二进制表示从最低位开始计数为 0 - 31,有如下问题: 确定其二进制表示的第 x 位是 0 还是 1: 将第 x 位修改为 1: 将第 x 位修改为 0: lowbit 运算(提取最低位的 1): 将最右侧的 1 变为 0: 可以用 n 减去 lowbit 运算后的结果,也可以使用下面的方法
2025-07-24 14:01:12
901
原创 [算法基础] 二分查找
二分查找算法利用数组的二段性,也就是数组可以被根据一定的规律分为两段,这两段具有不同的性质。时间复杂度:1次循环 ---> 剩 n/22次循环 ---> 剩 n/43次循环 ---> 剩 n/8......m次循环 ---> 剩 11 = n/n = n/(2的m次方)m = logn。
2025-07-21 17:07:35
902
原创 [算法基础] 双指针与滑动窗口
定义扫描指针,该指针需要扫描整个数组。定义目标指针,这个指针是否移动要根据扫描指针碰到的一些条件来判断。需要保证目标指针始终指向一个目的位置,在这道题里需要它始终指向第一个 0 元素,这样就使得整个数组可以被分成两部分,目的指针前均为非 0 元素,目的指针后均是 0 元素。
2025-07-21 16:58:08
868
原创 [Mybatis-Plus] Mybatis-Plus 基础
首先,mybatisplus 支持全部 mybatis 自定义 sql 的方式(mybatisplus 只对 mybatis 做升级而不做改动)。在这个基础上,mybatisplus 的 Wrapper 也为自定义 sql 提供了支持。@Mapper// 上层代码调用该方法时,需传入 Wrapper 实例作为条件构造参数</select>@Test// 构造 Wrapper 对象,作为参数传入 Mapper 层// 调用 Mapper 层@Mapper</update>@Test。
2025-07-08 10:44:25
793
原创 [Mybatis] 初识 Mybatis 与基础使用
简单来说,Mybatis 是一个持久层框架,用于操作数据库,使开发者更简单地完成程序和数据库之间的交互。开始使用 Mybatis:1. 创建 SpringBoot 项目,导入 Mybatis 的依赖和 Mybatis 支持的数据库的依赖。3. 创建实体类用于接收某一数据表的数据,属性与表头字段一一对应4. 开始写 Dao/Mapper 层接口。
2025-07-07 22:33:24
862
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人
RSS订阅