更新站点配置,修改标题、子标题和语言设置;删除多个示例文章和指南文件;更新关于页面内容,添加个人介绍和技术栈信息。

This commit is contained in:
2025-07-18 22:41:47 +08:00
parent a009f760d6
commit d1128370d3
22 changed files with 17094 additions and 687 deletions

View File

@@ -8,11 +8,11 @@ import type {
import { LinkPreset } from "./types/config"; import { LinkPreset } from "./types/config";
export const siteConfig: SiteConfig = { export const siteConfig: SiteConfig = {
title: "Fuwari", title: "MeowRai's Blog",
subtitle: "Demo Site", subtitle: "A blog about my life",
lang: "en", // 'en', 'zh_CN', 'zh_TW', 'ja', 'ko', 'es', 'th' lang: "zh_CN", // 'en', 'zh_CN', 'zh_TW', 'ja', 'ko', 'es', 'th'
themeColor: { themeColor: {
hue: 250, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345 hue: 285, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
fixed: false, // Hide the theme color picker for visitors fixed: false, // Hide the theme color picker for visitors
}, },
banner: { banner: {
@@ -27,7 +27,7 @@ export const siteConfig: SiteConfig = {
}, },
toc: { toc: {
enable: true, // Display the table of contents on the right side of the post enable: true, // Display the table of contents on the right side of the post
depth: 2, // Maximum heading depth to show in the table, from 1 to 3 depth: 3, // Maximum heading depth to show in the table, from 1 to 3
}, },
favicon: [ favicon: [
// Leave this array empty to use the default favicon // Leave this array empty to use the default favicon
@@ -46,16 +46,16 @@ export const navBarConfig: NavBarConfig = {
LinkPreset.About, LinkPreset.About,
{ {
name: "GitHub", name: "GitHub",
url: "https://github.com/saicaca/fuwari", // Internal links should not include the base path, as it is automatically added url: "https://github.com/meowrain", // Internal links should not include the base path, as it is automatically added
external: true, // Show an external link icon and will open in a new tab external: true, // Show an external link icon and will open in a new tab
}, },
], ],
}; };
export const profileConfig: ProfileConfig = { export const profileConfig: ProfileConfig = {
avatar: "assets/images/demo-avatar.png", // Relative to the /src directory. Relative to the /public directory if it starts with '/' avatar: "https://blog.meowrain.cn/api/i/2025/07/18/zn3t6t-1.webp", // Relative to the /src directory. Relative to the /public directory if it starts with '/'
name: "Lorem Ipsum", name: "MeowRain_Offical",
bio: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", bio: "A developer who loves to code and learn new things,build code for love❤ and fun🎉",
links: [ links: [
{ {
name: "Twitter", name: "Twitter",

View File

@@ -0,0 +1,118 @@
---
title: JVM GC相关参数
published: 2025-07-18
description: ''
image: ''
tags: [GC,JAVA,JVM]
category: 'Java'
draft: false
lang: ''
---
# GC相关参数
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10kn2xz-1.webp)
---
### 1. 堆初始大小 (`Xms`)
- **参数:** `Xms<size>`
- **含义:** 设置 JVM 启动时**初始分配的堆内存大小**。
- **作用:**
- 决定了 Java 程序一启动时JVM 向操作系统申请的内存大小。
- 如果设置得太小JVM 可能会在程序运行初期频繁地进行堆内存扩展,这会带来一定的性能开销。
- **示例:** `Xms512m` 表示设置初始堆大小为 512MB。
- **最佳实践:** 在生产环境中,通常建议将 `Xms``Xmx` 设置为相同的值,以避免堆的动态扩展和收缩带来的性能抖动。
---
### 2. 堆最大大小 (`Xmx` 或 `XX:MaxHeapSize`)
- **参数:** `Xmx<size>``XX:MaxHeapSize=<size>`
- **含义:** 设置 JVM **允许分配的最大堆内存大小**
- **作用:**
- 这是堆内存的硬性上限。如果应用程序需要的内存超过了这个值,就会抛出 `java.lang.OutOfMemoryError`
- 合理设置此值可以防止应用程序因内存泄漏等问题耗尽所有服务器内存,从而影响其他进程。
- **示例:** `Xmx2g` 表示设置最大堆大小为 2GB。
---
### 3. 新生代大小 (`Xmn` 或 `XX:NewSize` + `XX:MaxNewSize`)
- **参数:** `Xmn<size>`
- **含义:** 设置**新生代Young Generation的大小**。这是一个快捷参数。
- **作用:**
- 新生代是绝大多数新对象产生的地方,也是 Minor GC 发生的主要区域。
- 设置一个合理的新生代大小非常重要。
- **过小:** 会导致 Minor GC 过于频繁。
- **过大:** 会挤占老年代的空间,可能导致更频繁的 Full GC。同时单次 Minor GC 的时间可能会变长。
- **补充:** `Xmn` 实际上是同时设置了 `XX:NewSize`(新生代初始大小)和 `XX:MaxNewSize`(新生代最大大小)。如果希望新生代大小动态变化,可以分别设置这两个参数。
---
### 4. 幸存区比例 (`XX:SurvivorRatio`)
- **参数:** `XX:SurvivorRatio=<ratio>`
- **含义:** 设置新生代中 **Eden 区与一个 Survivor 区的大小比例**
- **计算公式:** `ratio = Eden区大小 / Survivor区大小`
- **作用:**
- 这个比例决定了新生代中用于创建新对象Eden和存放幸存对象Survivor的空间分配。
- 例如,`XX:SurvivorRatio=8` 表示 Eden:S0:S1 的比例是 8:1:1。这意味着 Eden 区将占用新生代 8/10 的空间,而每个 Survivor 区占用 1/10。
- **注意:** 这个参数在启用了自适应大小策略(`XX:+UseAdaptiveSizePolicy`,在某些 GC 算法中默认开启)时,其设置的固定比例可能会被 JVM 动态调整。
---
### 5. 幸存区比例 (动态) (`XX:InitialSurvivorRatio` 和 `XX:+UseAdaptiveSizePolicy`)
- **参数:** `XX:+UseAdaptiveSizePolicy`
- **含义:** **启用 GC 自适应大小策略**。这个策略允许 JVM 根据应用程序的运行情况(如吞吐量、停顿时间目标)动态调整堆中各区域的大小,包括 Eden/Survivor 的比例。
- **作用:**
- 开启后JVM 会自动优化内存分配,省去了手动精细调优的麻烦。这是 Parallel GC 等收集器默认开启的。
- `XX:InitialSurvivorRatio` 用于设定自适应策略下的**初始** SurvivorRatio 值,后续 JVM 可能会根据需要进行调整。
- **结论:** 如果你看到这个参数,意味着 JVM 正在自动管理新生代的比例,`XX:SurvivorRatio` 的静态设置可能不会生效。
---
### 6. 晋升阈值 (`XX:MaxTenuringThreshold`)
- **参数:** `XX:MaxTenuringThreshold=<threshold>`
- **含义:** 设置对象从新生代晋升到老年代的**年龄阈值**。
- **作用:**
- 一个对象在 Survivor 区每熬过一次 Minor GC其年龄就加 1。当年龄达到这个阈值时就会被移动到老年代。
- 默认值通常是 15或 6取决于 GC
- 如果设置得太高,对象可能长时间停留在 Survivor 区,增加了复制成本;如果设置得太低,可能导致一些生命周期不长的对象过早进入老年代,增加了 Full GC 的压力。
---
### 7. 晋升详情 (`XX:+PrintTenuringDistribution`)
- **参数:** `XX:+PrintTenuringDistribution`
- **含义:** 一个诊断参数,用于在每次 Minor GC 后**打印出 Survivor 区中对象的年龄分布情况**。
- **作用:**
- 这是调优 `XX:MaxTenuringThreshold` 的重要工具。
- 通过观察日志,你可以看到每个年龄段有多少对象,以及 JVM 计算出的动态晋升阈值,从而判断当前设置是否合理。
---
### 8. GC 详情 (`XX:+PrintGCDetails` 和 `verbose:gc`)
- **参数:** `XX:+PrintGCDetails``verbose:gc`
- **含义:** 打印详细的 GC 日志信息。
- **作用:**
- 这是进行 GC 性能分析和故障排查的**必备参数**。
- `verbose:gc` 是一个标准参数,输出基本的 GC 信息。
- `XX:+PrintGCDetails` 会提供更详尽的信息,包括每次 GC 前后堆各区域的大小、GC 耗时等。
- **推荐:** 通常与 `XX:+PrintGCTimeStamps``XX:+PrintGCDateStamps` 一起使用,为日志增加时间戳。
---
### 9. FullGC 前 MinorGC (`XX:+ScavengeBeforeFullGC`)
- **参数:** `XX:+ScavengeBeforeFullGC`
- **含义:** 指示 JVM 在执行 Full GC 之前,先强制进行一次 Minor GC。
- **作用:**
- 理论上,这可以清理掉新生代中大部分可以被回收的对象,从而减轻 Full GC 的负担,因为 Full GC 需要处理整个堆(包括新生代)。
- **注意:**
- 此参数在现代的 GC如 G1中已不推荐使用或被废弃因为它们有更智能的回收策略。
- 在某些情况下它可能会引入一次额外的、不必要的停顿Minor GC 的停顿)。因此,除非有明确的测试数据支持,否则一般不建议开启。

View File

@@ -0,0 +1,601 @@
---
title: HashMap原理
published: 2025-07-18
description: ''
image: ''
tags: [HashMap,Java]
category: 'Java'
draft: false
lang: ''
---
# 说说HashMap的原理
HashMap是基于哈希表的数据结构用于存储键值对。
核心是将键的哈希值映射到数组索引位置,通过数组+链表+红黑树来解决哈希冲突。
HashMap使用键的hashCode()方法计算哈希值,通过`(n-1) &hash`确定元素在数组中的存储位置。
哈希值是经过一定的扰动处理的,防止哈希值分布不均,从而减少冲突,
HashMap的默认初始容量为16负载因子为0.75也就是说当存储的元素数量超过16 * 0.75 = 12个的时候HashMap会触发扩容操作容量x2并重新分配元素位置这种扩容是比较耗时的操作频繁扩容会影响性能。
# 通过源码深入了解HashMap
```java
// 默认初始容量 - 必须是 2 的幂次方。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 即 16
// 最大容量,如果构造函数中通过参数隐式指定了更高的值,则使用此最大容量。
// 必须是小于等于 1 << 30 的 2 的幂次方。
// 由于你可以随时指定非常大甚至超过了1亿的值为了防止内存溢出或数组长度无效HashMap内部通过MAXIMUM_CAPACITY做了一个“保险”来确保容量不会超过某个安全极限。
static final int MAXIMUM_CAPACITY = 1 << 30;
// 构造函数中未指定时使用的负载因子。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 在向存储单元添加元素时,存储单元使用树结构而不是链表结构的存储单元计数阈值。
// 当向存储单元添加元素,且该存储单元至少有此数量的节点时,存储单元将转换为树结构。
// 该值必须大于 2并且应该至少为 8以与移除元素时转换回普通存储单元的假设相匹配。
static final int TREEIFY_THRESHOLD = 8;
// 在调整大小操作期间将(拆分的)存储单元转换为非树结构存储单元的存储单元计数阈值。
// 应该小于 TREEIFY_THRESHOLD并且最多为 6以与移除元素时的收缩检测相匹配。
static final int UNTREEIFY_THRESHOLD = 6;
// 存储单元可以树化的最小表容量。
// (否则,如果存储单元中有太多节点,表将进行扩容。)
// 应该至少是 4 * TREEIFY_THRESHOLD以避免扩容和树化阈值之间的冲突。
static final int MIN_TREEIFY_CAPACITY = 64;
```
![](https://blog.meowrain.cn/api/i/2025/06/13/nm8tvy-0.webp)
# HashMap的存储结构
从源码上看HashMap的每个存储单元都是一个链表或者红黑树也就是下面的Node类那么我们可以用下面的图来展示一个完成初始化的HashMap的存储结构。
![](https://blog.meowrain.cn/api/i/2025/06/13/nnlf4v-0.webp)
### 为什么采用数组?
因为数组的随机访问速度非常快HashMap通过哈希函数将键映射到数组索引位置从而实现快速查找。
数组的每一个元素称为一个桶bucket对于一个给定的键值对key,valueHashMap会计算出一个哈希值计算的是key的hash然后通过`(n-1) & hash`来确定该键值对在数组中的位置。
### 如何定位key value该存储在桶数组的哪个位置上获取index
HashMap通过`(n - 1) & hash`来计算索引位置其中n是数组的长度hash是键的哈希值。
### 如何计算hash值
HashMap使用键的`hashCode()`方法计算哈希值,然后对哈希值进行扰动处理,最后通过`(n-1) & hash`来确定元素在数组中的存储位置。
### 为什么要扰动处理?
扰动处理是为了减少哈希冲突防止哈希值分布不均。HashMap会对哈希值进行扰动处理以确保不同的键能够更均匀地分布在数组中从而减少冲突。
在Java 8中HashMap使用了一个扰动函数来优化hash值的分布
```java
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
```
这个函数的作用是:
1. 首先获取key的hashCode()值
2. 将hashCode的高16位与低16位进行异或运算
### 为什么用的是&运算而不是取模运算?
在java中我们会让HashMap的容量是2的幂次方这样可以通过`(n-1) & hash`来快速计算出索引位置,避免了取模运算的性能开销。
这里`(n - 1) & hash` == `hash % n`,但&运算比取模运算更高效。
n是数组的长度hash是键的哈希值。
### 为什么要让HashMap的容量是2的幂次方
因为当容量是2的幂次方时`(n-1) & hash`可以快速计算出索引位置,而不需要进行取模运算。
![](https://blog.meowrain.cn/api/i/2025/06/13/nqocqh-0.webp)
### 为什么会用到链表?
我们在HashMap的使用过程中可能会遇到哈希冲突的情况也就是不同的键经过哈希函数计算后得到了相同的索引位置使用链表我们可以把这些冲突的键值对存储在同一个桶中用链表连接在一起jdk8开始链表节点不再使用头插法而是使用尾插法这样可以减少链表的长度提升查找效率。
头插法还可能造成链表形成环形,导致死循环。
![](https://blog.meowrain.cn/api/i/2025/06/13/nva4ft-0.webp)
## Node
```java
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
return o instanceof Map.Entry<?, ?> e
&& Objects.equals(key, e.getKey())
&& Objects.equals(value, e.getValue());
}
}
```
# HashMap的Put方法
HashMap的put方法是用来添加键值对到HashMap中的核心方法。它的实现逻辑如下
```java
/**
* 实现 Map.put 和相关方法。
*
* @param hash key的哈希值
* @param key 键
* @param value 要放入的值
* @param onlyIfAbsent 如果为 true则不更改现有值
* @param evict 如果为 false则表处于创建模式。
* @return 返回先前的值,如果没有则返回 null
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) { // 📌 定义 putVal 方法,用于将键值对放入 HashMap
Node<K,V>[] tab; Node<K,V> p; int n, i; // 🏷️ 声明局部变量tab (哈希表数组), p (当前节点), n (数组长度), i (数组索引)
// 检查哈希表是否为空或长度为0
if ((tab = table) == null || (n = tab.length) == 0)
// 🏗️ 如果为空,则调用 resize() 方法初始化或扩容哈希表,并获取新的长度
n = (tab = resize()).length;
// 🎯 计算键在哈希表中的索引位置 i并检查该位置是否为空
if ((p = tab[i = (n - 1) & hash]) == null)
// ✨ 如果为空,直接在该位置创建一个新节点
tab[i] = newNode(hash, key, value, null);
else { // 🤔 如果该位置不为空(发生哈希冲突)
Node<K,V> e; K k; // 🏷️ 声明局部变量e (用于找到的已存在节点或新节点), k (临时键)
// 🔑 检查桶中第一个节点的哈希值和键是否与要插入的键值对匹配
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// ✅ 如果匹配,将 e 指向该节点 p (表示键已存在)
e = p;
// 🌳 检查桶中的节点是否为 TreeNode (红黑树节点)
else if (p instanceof TreeNode)
// 🌲 如果是红黑树,调用 putTreeVal 方法将键值对插入红黑树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else { // 🔗 如果是链表
// 🔄 遍历链表
for (int binCount = 0; ; ++binCount) {
// نهاية 检查当前节点的下一个节点是否为空 (到达链表尾部)
if ((e = p.next) == null) {
// 在链表尾部插入新节点
p.next = newNode(hash, key, value, null);
// 🌲❓ 检查链表长度是否达到树化阈值 (TREEIFY_THRESHOLD - 1 因为 binCount 从0开始计数且当前p是尾部的前一个节点)
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 🌳🔗➡️🌳 如果达到阈值,将链表转换为红黑树
treeifyBin(tab, hash);
break; // 🛑 跳出循环,因为新节点已插入
}
// 🔑 检查链表中节点的哈希值和键是否与要插入的键值对匹配
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break; // ✅ 如果匹配,跳出循环 (表示键已存在e 指向该节点)
// 👉 将 p 指向下一个节点,继续遍历
p = e;
}
}
// 🔑❓ 检查 e 是否不为 null (表示键已存在于哈希表中,或者在红黑树中找到了/插入了节点)
if (e != null) { // existing mapping for key
// 💾 获取旧值
V oldValue = e.value;
// 🔄❓ 根据 onlyIfAbsent 参数决定是否更新值 (如果 onlyIfAbsent 为 false或者旧值为 null则更新)
if (!onlyIfAbsent || oldValue == null)
// ⬆️ 更新节点的值
e.value = value;
// 🔗 回调方法,用于 LinkedHashMap 等子类记录节点访问
afterNodeAccess(e);
// ↩️ 返回旧值
return oldValue;
}
}
// 🛠️ 修改计数器加1用于迭代器快速失败机制
++modCount;
// 📈 检查当前元素数量是否超过阈值 (threshold = capacity * loadFactor)
if (++size > threshold)
// 🏗️ 如果超过阈值,调用 resize() 方法扩容哈希表
resize();
// 🔗 回调方法,用于 LinkedHashMap 等子类记录节点插入
afterNodeInsertion(evict);
// ↩️ 如果是新插入的键值对,返回 null
return null;
}
```
![](https://blog.meowrain.cn/api/i/2025/06/13/nzkmzk-0.webp)
# HashMap的Get方法
```java
/**
* 实现 Map.get 和相关方法。
*
* @param key 要查找的键
* @return 返回找到的节点,如果没有找到则返回 null
*/
final Node<K,V> getNode(Object key) { // 📌 定义 getNode 方法,用于根据键查找节点
Node<K,V>[] tab; Node<K,V> first, e; int n, hash; K k; // 🏷️ 声明局部变量tab (哈希表数组), first (桶中第一个节点), e (当前节点), n (数组长度), hash (键的哈希值), k (临时键)
// 🔍 检查哈希表是否不为空且长度大于0并且根据键的哈希值计算出的桶位置有节点
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & (hash = hash(key))]) != null) {
// 🎯 首先检查桶中第一个节点的哈希值和键是否与要查找的键匹配
if (first.hash == hash && // always check first node 总是先检查第一个节点
((k = first.key) == key || (key != null && key.equals(k))))
// ✅ 如果匹配,直接返回第一个节点
return first;
// 🔗 检查第一个节点是否有下一个节点(链表或红黑树)
if ((e = first.next) != null) {
// 🌳 如果第一个节点是 TreeNode红黑树节点
if (first instanceof TreeNode)
// 🌲 在红黑树中查找并返回节点
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
// 🔄 如果是链表,遍历链表查找节点
do {
// 🔑 检查当前节点的哈希值和键是否与要查找的键匹配
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// ✅ 如果匹配,返回当前节点
return e;
// 👉 移动到下一个节点,继续遍历直到链表末尾
} while ((e = e.next) != null);
}
}
// ❌ 如果没有找到匹配的节点,返回 null
return null;
}
```
![](https://blog.meowrain.cn/api/i/2025/06/13/o2aa3y-0.webp)
# HashMap的扩容
HashMap的扩容是指当存储的元素数量超过负载因子所允许的最大数量时HashMap会自动增加其容量。
扩容的过程包括以下几个步骤:
1. **计算新的容量**:新的容量通常是当前容量的两倍。
2. **创建新的数组**:创建一个新的数组来存储扩容后的元素。
3. **重新计算索引位置**:对于每个元素,重新计算其在新数组中的索引位置,并将其移动到新数组中。
源码中是resize()函数
```java
/**
* 初始化或将表大小扩大一倍。如果为null则根据字段threshold中保存的初始容量目标进行分配。
* 否则因为我们使用的是2的幂次方扩展每个桶中的元素必须保持在相同的索引位置
* 或者在新表中以2的幂次方偏移量移动。
*
* @return 返回新的哈希表
*/
final Node<K,V>[] resize() { // 📏 定义扩容方法
Node<K,V>[] oldTab = table; // 🗂️ 保存旧的哈希表引用
int oldCap = (oldTab == null) ? 0 : oldTab.length; // 📊 获取旧表的容量如果为null则容量为0
int oldThr = threshold; // 📋 保存旧的阈值
int newCap, newThr = 0; // 🆕 声明新容量和新阈值变量
// 🔍 如果旧容量大于0表已初始化
if (oldCap > 0) {
// ⚠️ 如果旧容量已达到最大值,则不再扩容
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE; // 🔢 将阈值设为最大整数值
return oldTab; // ↩️ 直接返回旧表,不扩容
}
// 🔢 新容量 = 旧容量 * 2且不超过最大容量且旧容量 >= 默认初始容量
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // 📈 新阈值 = 旧阈值 * 2
}
// 🎯 如果旧容量为0但旧阈值大于0通过构造函数指定了初始容量
else if (oldThr > 0)
newCap = oldThr; // 🆕 新容量等于旧阈值
// 🌟 如果旧容量和旧阈值都为0使用默认值初始化
else {
newCap = DEFAULT_INITIAL_CAPACITY; // 🔢 新容量设为默认初始容量16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 📊 新阈值 = 0.75 * 16 = 12
}
// 🔧 如果新阈值为0需要重新计算
if (newThr == 0) {
float ft = (float)newCap * loadFactor; // 📐 计算新阈值 = 新容量 * 负载因子
// ✅ 确保新阈值不超过最大值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr; // 📋 更新阈值
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // 🏗️ 创建新的哈希表数组
table = newTab; // 🔄 将新表赋值给table字段
// 📦 如果旧表不为空,需要转移元素
if (oldTab != null) {
// 🔄 遍历旧表的每个桶
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e; // 🏷️ 当前节点
// 🔍 如果当前桶不为空
if ((e = oldTab[j]) != null) {
oldTab[j] = null; // 🧹 清空旧桶帮助GC
// 🔗 如果桶中只有一个节点(没有链表或红黑树)
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e; // 🎯 直接重新计算位置并放入新表
// 🌳 如果是红黑树节点
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap); // 🌲 调用红黑树的分割方法
// 🔗 如果是链表
else {
Node<K,V> loHead = null, loTail = null; // 🔻 低位链表的头和尾节点
Node<K,V> hiHead = null, hiTail = null; // 🔺 高位链表的头和尾节点
Node<K,V> next; // ➡️ 下一个节点
// 🔄 遍历链表中的所有节点
do {
next = e.next; // 📍 保存下一个节点
// 🎲 通过 (e.hash & oldCap) 判断节点应该放在哪个位置
if ((e.hash & oldCap) == 0) {
// 🔻 放在原位置(低位链表)
if (loTail == null)
loHead = e; // 🎯 如果低位链表为空,设置头节点
else
loTail.next = e; // 🔗 连接到低位链表尾部
loTail = e; // 📍 更新尾节点
}
else {
// 🔺 放在原位置+oldCap的位置高位链表
if (hiTail == null)
hiHead = e; // 🎯 如果高位链表为空,设置头节点
else
hiTail.next = e; // 🔗 连接到高位链表尾部
hiTail = e; // 📍 更新尾节点
}
} while ((e = next) != null); // 🔄 继续遍历直到链表末尾
// 🔻 如果低位链表不为空,放入原位置
if (loTail != null) {
loTail.next = null; // ✂️ 断开链表尾部
newTab[j] = loHead; // 📍 放入新表的原位置
}
// 🔺 如果高位链表不为空,放入新位置
if (hiTail != null) {
hiTail.next = null; // ✂️ 断开链表尾部
newTab[j + oldCap] = hiHead; // 📍 放入新表的 j + oldCap 位置
}
}
}
}
}
return newTab; // ↩️ 返回新的哈希表
}
```
## 扩容的时候高位和低位链表详解
```java
else {
Node<K,V> loHead = null, loTail = null; // 🔻 低位链表的头和尾节点
Node<K,V> hiHead = null, hiTail = null; // 🔺 高位链表的头和尾节点
Node<K,V> next; // ➡️ 下一个节点
// 🔄 遍历链表中的所有节点
do {
next = e.next; // 📍 保存下一个节点
// 🎲 通过 (e.hash & oldCap) 判断节点应该放在哪个位置
if ((e.hash & oldCap) == 0) {
// 🔻 放在原位置(低位链表)
if (loTail == null)
loHead = e; // 🎯 如果低位链表为空,设置头节点
else
loTail.next = e; // 🔗 连接到低位链表尾部
loTail = e; // 📍 更新尾节点
}
else {
// 🔺 放在原位置+oldCap的位置高位链表
if (hiTail == null)
hiHead = e; // 🎯 如果高位链表为空,设置头节点
else
hiTail.next = e; // 🔗 连接到高位链表尾部
hiTail = e; // 📍 更新尾节点
}
} while ((e = next) != null); // 🔄 继续遍历直到链表末尾
// 🔻 如果低位链表不为空,放入原位置
if (loTail != null) {
loTail.next = null; // ✂️ 断开链表尾部
newTab[j] = loHead; // 📍 放入新表的原位置
}
// 🔺 如果高位链表不为空,放入新位置
if (hiTail != null) {
hiTail.next = null; // ✂️ 断开链表尾部
newTab[j + oldCap] = hiHead; // 📍 放入新表的 j + oldCap 位置
}
}
```
### 核心原理
当HashMap从容量n扩容到2n时每个元素的新位置只有两种可能
- **保持原位置**(低位链表)
- **移动到原位置+n**(高位链表)
判断依据: `(e.hash & oldCap) == 0`如果为0则放在原位置否则放在原位置+n。 n是旧容量。
- 低位链表lo list满足 `(e.hash & oldCap) == 0` 的节点,扩容后**继续放在原位置** `j`
- 高位链表hi list满足 `(e.hash & oldCap) != 0` 的节点,扩容后放在新位置 `j + oldCap`
#### 举例子
假设oldCap = 16,newCap = 32
oldCap=16 // 10000
newCap=32 // 100000
```
hash1 = 5; // 000101
扩容前(cap = 16)计算index
index1 = hash1 & (oldCap - 1) ==> 5 & 15
000101
&000111
`--------`
000101 ==> 5
扩容后(cap = 32)计算index
我们要看要不要移动这个kv到桶中的新位置
判断 (hash & oldCap) == 0
hash1 & oldCap ==> 5 & 16
00000101
& 00010000
`-----------`
00000000 ==> 0
所以这个kv会放在原位置5
```
再举个例子
```
hash2 = 20; // 10100
扩容前(cap = 16)计算index
index2 = hash2 & (oldCap - 1) ==> 20 & 15
10100
& 01111
--------------
00100 ==> 4
扩容后(cap = 32)计算index
要先看hash & oldCap == 0 ?
hash2 & oldCap ==> 20 & 16
00010100
& 00010000
--------------
00010000 ==> 16
不为0所以这个20会放在新的位置 原来的位置+ 旧桶数组容量 = 4 + 16 = 20
```
你的理解非常正确!🎉👍
你已经掌握了HashMap扩容时分桶位置变更的本质原理让我们用你的描述稍作归纳和梳理验证你的思路
---
### 为什么判断的是与oldCap相与得到的值是1还是0来决定搬迁位置
当HashMap扩容时容量从 `oldCap` 扩展到 `newCap`,比如从 16 扩展到 32。
- 原来 HashMap 的下标计算是:`index = hash & (oldCap-1)`,比如 `00001111`低4位
- 扩容后,计算下标变为:`index = hash & (newCap-1)`,比如 `00011111`低5位也就是多了一位。
-`oldCap`(如 `00010000`)相与,就相当于“掐头去尾”地只关注扩容新增的那一位:
- 如果 `(hash & oldCap) == 0`说明这位是0**扩容后的位置等于原index**
- 如果 `(hash & oldCap) != 0`说明这位是1**扩容后的位置等于原index + oldCap**
- 这种判断让你高效知道节点该不该搬迁以及搬去哪里无需重新完全计算index。
---
#### 举例验证(巩固印象)
假如:
- oldCap = 16 ⇒ 00010000
- oldCap-1 = 15 ⇒ 00001111
- newCap = 32 ⇒ 00100000
- newCap-1 = 31 ⇒ 00011111
- hash = 21 ⇒ 10101
**扩容前下标:**
```java
index = 10101 & 01111 = 00101 = 5
```
**扩容后下标:**
```java
index = 10101 & 11111 = 10101 = 21
```
**oldCap这一位的判断**
```java
10101 & 10000 = 10000 0
```
说明这位是1扩容后下标变成原index+16=21。
---
### 扩容的条件是什么?
当 HashMap 中存储的元素数量超过了「阈值」threshold就会进行扩容。
这个「阈值」的计算公式是:
```
threshold = capacity * loadFactor
```
loadFactor 是负载因子,默认值为 0.75。
### 为什么要进行搬迁呢?
HashMap扩容的主要目的是
减少哈希冲突,提高查找、插入效率。
让更多桶可用,降低碰撞链表队列的长度。
# jdk1.7和jdk1.8中hashmap的区别
![](https://blog.meowrain.cn/api/i/2025/06/13/pf960q-0.webp)
![](https://blog.meowrain.cn/api/i/2025/06/13/pfb9eb-0.webp)
![](https://blog.meowrain.cn/api/i/2025/06/13/pfedpt-0.webp)
![](https://blog.meowrain.cn/api/i/2025/06/13/pfqbnm-0.webp)
![](https://blog.meowrain.cn/api/i/2025/06/13/pfyw7c-0.webp)
# 链表什么时候转红黑树?
桶数组中某个桶的链表长度>=8 而且桶数组长度> 64的时候hashmap会转换为红黑树
![](https://blog.meowrain.cn/api/i/2025/06/25/hi2z5e-0.webp)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
---
title: JVM垃圾回收算法
published: 2025-07-18
description: ''
image: ''
tags: [JVM,垃圾回收,分代回收]
category: 'Java'
draft: false
lang: ''
---
# 垃圾回收算法
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10nn4ww-1.webp)
[【Java虚拟机】JVM垃圾回收机制和常见回收算法原理-腾讯云开发者社区-腾讯云](https://cloud.tencent.com/developer/article/2292267)
### **垃圾回收机制**
**1什么是垃圾回收机制Garbage Collection 简称GC)**
- 指自动管理动态分配的内存空间的机制,自动回收不再使用的内存,以避免内存泄漏和内存溢出的问题
- 最早是在1960年代提出的程序员需要手动管理内存的分配和释放
- 这往往会导致内存泄漏和内存溢出等问题同时也增加了程序员的工作量特别是C++/C语言开发的时候
- Java语言是最早实现垃圾回收机制的语言之一其他编程语言如C#、Python和Ruby等也都提供了垃圾回收机制
**2JVM自动垃圾回收机制**
- 指Java虚拟机在运行Java程序时自动回收不再使用的对象所占用的内存空间的过程
- Java程序中的对象一旦不再被引用会被标记为垃圾对象JVM会在适当的时候自动回收这些垃圾对象所占用的内存空间
- 优点
- 减少了程序员的工作量,不需要手动管理内存
- 动态地管理内存,根据应用程序的需要进行分配和回收,提高了内存利用率
- 避免内存泄漏和野指针等问题,增加程序的稳定性和可靠
- 缺点
- 垃圾回收会占用一定的系统资源,可能会影响程序的性能
- 垃圾回收过程中会停止程序的执行,可能会导致程序出现卡顿等问题
- **不一定能够完全解决内存泄漏等问题,需要在编写代码时注意内存管理和编码规范**
---
# 垃圾回收算法
## 引用计数法
跟踪每个对象被引用的次数当引用次数为0 的时候,可以将该对象回收。
优点是实现简单,缺点是循环引用没办法回收,而且引用计数器消耗大。
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10nn7eb-1.webp)
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10nngi3-1.webp)
## **可达性分析算法**
- 可达性分析算法的基本思想是通过一系列的“GC Roots”对象作为起点进行搜索。
- 如果“GC Roots”和一个对象之间没有可达路径则称该对象是不可达的不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。
- 被判定为不可达的对象要成为回收对象,要至少经历两次标记过程。
- 如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。
通过一系列称为“GC Roots”的对象作为起始点从这些节点开始向下搜索搜索走过的路径称为“引用链”当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10nnmfv-1.webp)
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10nnpgs-1.webp)
### 什么是GC ROOT
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10nntly-1.webp)
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10nnyst-1.webp)
## **垃圾回收算法之标记-复制算法**
- 标记算法是一种常见的垃圾回收算法它的基本思路是将Java堆分为两个区域一个活动区域和一个空闲区域
- 在垃圾回收过程中,首先标记所有被引用的对象
- 然后将所有被标记的对象复制到空闲区域中,最后交换两个区域的角色,完成垃圾回收
- 标记复制算法的详细实现步骤
- 将Java堆分为两个区域一个活动区域和一个空闲区域初始时所有对象都分配在活动区域中
- 从GC Roots对象开始遍历整个对象图标记所有被引用的对象
- 对所有被标记存活的对象进行遍历,将它们复制到空闲区域中,并更新所有指向它们的引用,使它们指向新的地址
- 对所有未被标记的对象进行回收,将它们所占用的内存空间释放
- 交换活动区域和空闲区域的角色,空闲区域变为新的活动区域,原来的活动区域变为空闲区域
- 当空闲区域的内存空间不足时,进行一次垃圾回收,重复以上步骤。
- 优点
- 如果内存中的垃圾对象较多,需要复制的对象就较少,则效率高
- 清理后,内存碎片少
- 缺点
- 标记复制算法的效率较高,但是预留一半的内存区域用来存放存活的对象,占用额外的内存空间
- 如果出现存活对象数量比较多的时候,需要复制较多的对象 效率低
- 假如是在老年代区域99%的对象都是存活的,则性能底,所以老年代不适合这个算法
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10no08y-1.webp)
复制过程如下GC会将五个存活对象复制到to区并且保证在to区内存空间上的连续性。
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10no6pp-1.webp)
最后将from区中的垃圾对象清除。
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10nojzj-1.webp)
## **垃圾回收算法之标记-整理算法**
标记-整理算法Mark-Compact Algorithm 是一种常见的垃圾回收GC算法主要用于解决 标记-清除算法Mark-Sweep 产生的内存碎片问题。它通常被用于 Java 的老年代Old Generation垃圾回收中。
标记-整理算法主要分为两大阶段:
标记阶段Mark Phase
和标记-清除算法一样,从 GC Roots 出发,遍历所有可达对象,并将其标记为“存活”状态。
整理阶段Compact Phase
将所有存活对象向内存的一端移动(通常是低地址方向)。
移动后会更新对象引用地址,以保证程序继续正确运行。
移动完成后,直接清理边界以后的内存空间。
![](https://blog.meowrain.cn/api/i/2025/07/18/10pzxij-1.webp)
| **特点** | **标记-清除算法** | **标记-整理算法** |
| ------ | ------------- | ------------- |
| 内存碎片 | 会产生碎片 | 不会产生碎片 |
| 效率 | 清除快(只清除不可达对象) | 较慢(需要移动对象) |
| 适用场景 | 适用于对象回收率较高的情况 | 适用于对象存活率较高的情况 |
## 垃圾回收算法之-分代算法
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10noe2w-1.webp)
新生代分为eden区、from区、to区老年代是一整块内存空间
分代算法将内存区域分为两部分:新生代和老年代。
根据新生代和老年代中对象的不同特点使用不同的GC算法。
新生代对象的特点是:创建出来没多久就可以被回收(例如虚拟机栈中创建的对象,方法出栈就会销毁)。也就是说,每次回收时,大部分是垃圾对象,所以新生代适用于复制算法。
老年代的特点是经过多次GC依然存活。也就是说每次GC时大部分是存活对象所以老年代适用于标记压缩算法。
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10nomg3-1.webp)
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10nozio-1.webp)
### 分代算法执行过程
---

View File

@@ -0,0 +1,80 @@
---
title: Jvm分代回收机制
published: 2025-07-18
description: ''
image: ''
tags: [分代回收,JVM]
category: 'Java'
draft: false
lang: ''
---
# 分代回收
[juejin.cn](https://juejin.cn/post/7474503566154858536)
[【GC系列】JVM堆内存分代模型及常见的垃圾回收器-腾讯云开发者社区-腾讯云](https://cloud.tencent.com/developer/article/1755848)
[Eden与Survivor区 · Homurax's Blog](https://blog.homurax.com/2018/09/17/eden-survivor/)
[Java 虚拟机之垃圾收集](https://dunwu.github.io/waterdrop/pages/587898a0/)
[JVM内存分配策略](https://linqiankun.github.io/hexoblog/md/jvm/JVM%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E7%AD%96%E7%95%A5/)
现代JVM堆内存的典型划分
1. 年轻代Young Generation
2. 老年代Old Generation
3. 永久代/元空间Permanent Gen/Metaspace
## JDK7堆空间内部结构
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10mvpvg-1.webp)
特点:
永久代位于堆内存中
字符串常量池存放在永久代
方法区使用永久代实现
## JDK8堆空间内部结构
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10mvro2-1.webp)
永久代被元空间替换,元空间不属于堆内存。
元空间使用本地内存
字符串常量池移至堆内存
方法区改由元空间实现。
## 年轻代与老年代
JVM 内置的通用垃圾回收原则。堆内存划分为 Eden、Survivor(年轻代) Tenured/Old (老年代)空间:
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10mvw5e-1.webp)
核心规则:
1. 对象优先在Eden区分配
2. 大对象直接进入老年代
3. 长期存活对象进入老年代默认年龄阈值15
4. 动态年龄判断Survivor区中相同年龄对象总和超过50%时候晋升)
在 JVM 中,**年龄阈值Tenuring Threshold** 是一个关键的参数它决定了新生代Young Generation中的对象需要经历多少次垃圾回收Minor GC仍然存活才会被晋升Promotion到老年代Old Generation
年轻代分为Eden区和Survivor区Survivor区又分为S0,S1S0,S1其中一个作为使用区from),一个作为空闲区(to)不固定可能S0是空闲区也可能是使用区
在Minor GC开始以后会回收Eden区和使用区中的对象逃过第一轮GC的在Eden区和使用区中的对象会被丢在空闲区,接下来将使用区和空闲区互换空闲区变使用区使用区变空闲区等待下一次Eden区满进行Minor GC以此不断循环每复制一次年龄就会 + 1
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10mw6vd-1.webp)
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10mwdnp-1.webp)
# 堆空间大小设置
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10mwjcx-1.webp)
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10mwpa5-1.webp)

View File

@@ -0,0 +1,44 @@
---
title: Jvm常见垃圾收集器
published: 2025-07-18
description: ''
image: ''
tags: [Java,JVM,垃圾收集器]
category: 'Java'
draft: false
lang: ''
---
# Java中常见的垃圾收集器
GC收集器有哪些?
1.serial收集器
单线程工作时必须暂停其他工作线程。多用于client机器上使用复制算法
2.ParNew收集器
serial收集器的多线程版本server模式下虚拟机首选的新生代收集器。复制算法
3.Parallel Scavenge收集器
复制算法,可控制吞吐量的收集器。吞吐量即有效运行时间。
4.Serial Old收集器
serial的老年代版本使用整理算法。
5.Parallel Old收集器
第三种收集器的老年代版本,多线程,标记整理
6.CMS收集器
目标是最短回收停顿时间。
7.G1收集器,基本思想是化整为零将堆分为多个Region优先收集回收价值最大的Region。
[垃圾收集器_java垃圾收集器-CSDN博客](https://blog.csdn.net/binbinxyz/article/details/141821712)
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10lr31h-1.webp)
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10lrj5p-1.webp)
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10lrmof-1.webp)
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10lrvwa-1.webp)
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10ls1dm-1.webp)
# G1垃圾回收器
[G1垃圾回收](Java%E4%B8%AD%E5%B8%B8%E8%A7%81%E7%9A%84%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8%2022a49a1194e98020a75ced52b5d871d7/G1%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%2022a49a1194e980e9bbf7e2a6c0f3e4c6.md)

View File

@@ -0,0 +1,220 @@
---
title: MySQL共享锁和排他锁的区别
published: 2025-07-18
description: ''
image: ''
tags: [mysql锁]
category: 'MySQL'
draft: false
lang: 'zh-cn'
---
# 共享锁和排他锁的区别
MySQL中的**共享锁Shared Lock简称S锁**和**排他锁Exclusive Lock简称X锁**是InnoDB存储引擎用于并发控制的两种锁机制主要区别在于锁的兼容性和使用场景。以下是两者的详细对比
### 1. **定义**
- **共享锁S锁**:允许多个事务同时对同一数据加共享锁,用于读取数据,防止其他事务修改数据,但允许多个事务同时读取。
- **排他锁X锁**:只允许一个事务对数据加锁,用于修改数据,阻止其他事务对同一数据加任何锁(包括共享锁和排他锁)。
### 2. **锁的兼容性**
| 锁类型 | 共享锁S锁 | 排他锁X锁 |
| ----------------- | ------------- | ------------- |
| **共享锁S锁** | 兼容 | 不兼容 |
| **排他锁X锁** | 不兼容 | 不兼容 |
- **共享锁**多个事务可以同时持有同一数据的S锁适合并发读取。
- **排他锁**一旦某事务持有X锁其他事务无法对同一数据加S锁或X锁必须等待锁释放。
### 3. **使用场景**
- **共享锁**
- 用于只读操作,如`SELECT`查询。
- 典型场景:多个事务需要读取同一数据,但不修改(如报表查询)。
- 显式获取方式:`SELECT ... LOCK IN SHARE MODE`
- 示例:
允许多个事务同时读取`id = 1`的行,但阻止其他事务修改该行。
```sql
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
```
- **排他锁**
- 用于写操作,如`UPDATE`、`DELETE`、`INSERT`。
- 典型场景:需要修改数据并确保数据一致性(如库存扣减、余额更新)。
- 显式获取方式:`SELECT ... FOR UPDATE`。
- 示例:
锁定`id = 1`的行,阻止其他事务读取或修改,直到当前事务结束。
```sql
SELECT * FROM users WHERE id = 1 FOR UPDATE;
```
### 4. **获取方式**
- **共享锁**
- 自动某些情况下InnoDB在`SELECT`查询时可能隐式加S锁取决于隔离级别
- 显式:`SELECT ... LOCK IN SHARE MODE`。
- **排他锁**
- 自动:执行`UPDATE`、`DELETE`等写操作时InnoDB自动为受影响的行加X锁。
- 显式:`SELECT ... FOR UPDATE`。
### 5. **锁粒度**
- 两者都支持**行级锁**InnoDB默认和**表级锁**如MyISAM或特定操作
- 共享锁和排他锁在范围查询中可能涉及**间隙锁**或**下一键锁**Next-Key Lock用于防止幻读具体取决于事务隔离级别如`REPEATABLE READ`)。
### 6. **性能影响**
- **共享锁**:允许多个事务并发读取,适合读多写少的场景,阻塞较少。
- **排他锁**:阻止其他事务读写,适合写操作,但可能导致阻塞和死锁,尤其在高并发场景下。
### 7. **典型应用场景对比**
- **共享锁**:多个用户同时查看商品库存、生成报表等。
- **排他锁**:扣减库存、更新账户余额、防止并发修改导致数据不一致。
### 8. **死锁风险**
- **共享锁**死锁风险较低因为S锁之间兼容。
- **排他锁**死锁风险较高多个事务竞争X锁可能导致互相等待InnoDB会检测并回滚一个事务。
### 示例对比
假设有`products`表,字段包括`id`和`stock`
```sql
-- 事务A读取库存共享锁
BEGIN;
SELECT stock FROM products WHERE id = 1 LOCK IN SHARE MODE;
-- 其他事务可以同时读取,但不能修改
COMMIT;
-- 事务B扣减库存排他锁
BEGIN;
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
```
- 事务A的共享锁允许其他事务读取`id = 1`的行,但阻止修改。
- 事务B的排他锁阻止其他事务读取或修改`id = 1`的行直到事务B结束。
### 总结
- **共享锁**适合读操作,允许多个事务并发读取,强调高并发读性能。
- **排他锁**适合写操作,确保数据修改的独占性和一致性,但可能降低并发性能。
- 在实际应用中,结合业务需求、索引优化和事务隔离级别(如`READ COMMITTED`或`REPEATABLE READ`)合理选择锁类型,以平衡一致性和性能。
# 排他锁
MySQL的排他锁Exclusive Lock简称X锁是一种用于并发控制的锁机制确保在同一时间只有一个事务可以修改特定数据防止数据冲突和不一致。以下是对MySQL排他锁的详细讲解
### 1. **什么是排他锁?**
排他锁是MySQL中用于写操作的锁类型。当一个事务对某行、表或数据对象加了排他锁后其他事务无法对同一数据进行读共享锁或写排他锁操作直到该锁被释放。这保证了数据修改的原子性和一致性。
- **特点**
- 排他锁与任何其他锁(包括共享锁和排他锁)都不兼容。
- 持有排他锁的事务可以安全地修改数据,而不被其他事务干扰。
- 常用于`UPDATE`、`DELETE`、`INSERT`等写操作。
### 2. **排他锁的工作机制**
在MySQL的InnoDB存储引擎中默认支持事务和行级锁排他锁主要通过以下方式实现
- **行级锁**:锁住特定的行记录,只有被锁定的行无法被其他事务访问。
- **表级锁**在某些情况下如表级操作或MyISAM引擎锁住整个表。
- **间隙锁Gap Lock和下一键锁Next-Key Lock**:用于防止幻读,锁定某个范围的索引记录(常见于范围查询)。
当一个事务执行写操作(如`UPDATE`或`DELETE`InnoDB会自动为受影响的行加排他锁。例如
```sql
UPDATE users SET age = 30 WHERE id = 1;
```
MySQL会对`id = 1`的行加排他锁,直到事务提交(`COMMIT`)或回滚(`ROLLBACK`)才会释放锁。
### 3. **排他锁的获取方式**
- **隐式获取**通过DML操作如`UPDATE`、`DELETE`)自动加锁。例如:
```sql
UPDATE table_name SET column = value WHERE condition;
```
InnoDB会自动为受影响的行加排他锁。
- **显式获取**:使用`SELECT ... FOR UPDATE`语句显式加排他锁。例如:
```sql
SELECT * FROM users WHERE id = 1 FOR UPDATE;
```
这会锁定`id = 1`的行,阻止其他事务读取或修改该行,直到当前事务结束。
### 4. **排他锁的兼容性**
排他锁与其他锁的兼容性如下:
- **排他锁与排他锁**不兼容两个事务不能同时对同一数据加X锁。
- **排他锁与共享锁S锁**不兼容持有X锁的数据无法被其他事务加S锁读取。
- **结果**:排他锁会导致其他事务等待(阻塞),直到锁释放。
### 5. **排他锁的场景**
- **数据修改**:如`UPDATE`、`DELETE`操作,确保数据一致性。
- **防止并发冲突**:在高并发场景下,避免多个事务同时修改同一行导致数据不一致。
- **悲观锁机制**:通过`SELECT ... FOR UPDATE`实现悲观锁,适合需要严格控制并发访问的业务场景(如库存扣减)。
示例(库存扣减):
```sql
BEGIN;
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
-- 假设查询到stock=10
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
```
`FOR UPDATE`加排他锁,确保在事务期间其他事务无法修改`id = 1`的记录。
### 6. **可能的问题**
- **死锁**当多个事务互相等待对方持有的排他锁时可能发生死锁。InnoDB会自动检测死锁并回滚一个事务。
- **性能影响**:排他锁会阻塞其他事务,可能降低并发性能,尤其在高并发场景下。
- **锁范围过大**:若锁住的范围过大(例如表锁或范围查询的间隙锁),可能导致更多事务阻塞。
### 7. **如何优化排他锁的使用**
- **尽量使用行级锁**:确保查询条件使用索引,避免锁住过多行。
- **缩短事务时间**:尽快提交或回滚事务,减少锁的持有时间。
- **避免死锁**:按照固定顺序访问资源(如按表或主键顺序加锁)。
- **选择合适的隔离级别**:如降低隔离级别(从`REPEATABLE READ`到`READ COMMITTED`),减少间隙锁的使用。
### 8. **与共享锁的对比**
- **共享锁S锁**:允许多个事务同时读取数据,但不允许修改。常用于`SELECT ... LOCK IN SHARE MODE`。
- **排他锁X锁**:只允许一个事务修改数据,阻塞其他读写操作。
### 总结
MySQL的排他锁是确保数据写操作一致性的重要机制广泛用于事务性操作。通过合理设计查询和事务可以最大程度减少锁冲突和性能问题。在高并发场景下建议结合索引优化、事务管理以及合适的隔离级别来平衡一致性和性能。

View File

@@ -0,0 +1,42 @@
---
title: Nginx编译安装
published: 2025-07-18
description: ''
image: ''
tags: [Nginx编译安装]
category: 'Nginx'
draft: false
lang: 'zh-cn'
---
# nginx编译安装
```
wget https://nginx.org/download/nginx-1.28.0.tar.gz
tar -zxvf ninx-1.28.0.tar.gz
sudo apt install -y build-essential libtool zlib1g-dev openssl libpcre3 libpcre3-dev libssl-dev libgeoip-dev
sudo apt install libpcre2-dev
# 常用模块配置
./configure \
--prefix=/usr/local/nginx \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_gzip_static_module \
--http-client-body-temp-path=/var/temp/nginx/client \
--http-proxy-temp-path=/var/temp/nginx/proxy \
--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
--http-scgi-temp-path=/var/temp/nginx/scgi \
--with-stream \
--with-http_ssl_module \
--with-stream_ssl_preread_module # 新增:支持 ssl_preread 指令
make
make install
```

View File

@@ -1,22 +0,0 @@
---
title: Draft Example
published: 2022-07-01
tags: [Markdown, Blogging, Demo]
category: Examples
draft: true
---
# This Article is a Draft
This article is currently in a draft state and is not published. Therefore, it will not be visible to the general audience. The content is still a work in progress and may require further editing and review.
When the article is ready for publication, you can update the "draft" field to "false" in the Frontmatter:
```markdown
---
title: Draft Example
published: 2024-01-11T04:40:26.381Z
tags: [Markdown, Blogging, Demo]
category: Examples
draft: false
---

View File

@@ -1,311 +0,0 @@
---
title: Expressive Code Example
published: 2024-04-10
description: How code blocks look in Markdown using Expressive Code.
tags: [Markdown, Blogging, Demo]
category: Examples
draft: false
---
Here, we'll explore how code blocks look using [Expressive Code](https://expressive-code.com/). The provided examples are based on the official documentation, which you can refer to for further details.
## Expressive Code
### Syntax Highlighting
[Syntax Highlighting](https://expressive-code.com/key-features/syntax-highlighting/)
#### Regular syntax highlighting
```js
console.log('This code is syntax highlighted!')
```
#### Rendering ANSI escape sequences
```ansi
ANSI colors:
- Regular: Red Green Yellow Blue Magenta Cyan
- Bold: Red Green Yellow Blue Magenta Cyan
- Dimmed: Red Green Yellow Blue Magenta Cyan
256 colors (showing colors 160-177):
160 161 162 163 164 165
166 167 168 169 170 171
172 173 174 175 176 177
Full RGB colors:
ForestGreen - RGB(34, 139, 34)
Text formatting: Bold Dimmed Italic Underline
```
### Editor & Terminal Frames
[Editor & Terminal Frames](https://expressive-code.com/key-features/frames/)
#### Code editor frames
```js title="my-test-file.js"
console.log('Title attribute example')
```
---
```html
<!-- src/content/index.html -->
<div>File name comment example</div>
```
#### Terminal frames
```bash
echo "This terminal frame has no title"
```
---
```powershell title="PowerShell terminal example"
Write-Output "This one has a title!"
```
#### Overriding frame types
```sh frame="none"
echo "Look ma, no frame!"
```
---
```ps frame="code" title="PowerShell Profile.ps1"
# Without overriding, this would be a terminal frame
function Watch-Tail { Get-Content -Tail 20 -Wait $args }
New-Alias tail Watch-Tail
```
### Text & Line Markers
[Text & Line Markers](https://expressive-code.com/key-features/text-markers/)
#### Marking full lines & line ranges
```js {1, 4, 7-8}
// Line 1 - targeted by line number
// Line 2
// Line 3
// Line 4 - targeted by line number
// Line 5
// Line 6
// Line 7 - targeted by range "7-8"
// Line 8 - targeted by range "7-8"
```
#### Selecting line marker types (mark, ins, del)
```js title="line-markers.js" del={2} ins={3-4} {6}
function demo() {
console.log('this line is marked as deleted')
// This line and the next one are marked as inserted
console.log('this is the second inserted line')
return 'this line uses the neutral default marker type'
}
```
#### Adding labels to line markers
```jsx {"1":5} del={"2":7-8} ins={"3":10-12}
// labeled-line-markers.jsx
<button
role="button"
{...props}
value={value}
className={buttonClassName}
disabled={disabled}
active={active}
>
{children &&
!active &&
(typeof children === 'string' ? <span>{children}</span> : children)}
</button>
```
#### Adding long labels on their own lines
```jsx {"1. Provide the value prop here:":5-6} del={"2. Remove the disabled and active states:":8-10} ins={"3. Add this to render the children inside the button:":12-15}
// labeled-line-markers.jsx
<button
role="button"
{...props}
value={value}
className={buttonClassName}
disabled={disabled}
active={active}
>
{children &&
!active &&
(typeof children === 'string' ? <span>{children}</span> : children)}
</button>
```
#### Using diff-like syntax
```diff
+this line will be marked as inserted
-this line will be marked as deleted
this is a regular line
```
---
```diff
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+this is an actual diff file
-all contents will remain unmodified
no whitespace will be removed either
```
#### Combining syntax highlighting with diff-like syntax
```diff lang="js"
function thisIsJavaScript() {
// This entire block gets highlighted as JavaScript,
// and we can still add diff markers to it!
- console.log('Old code to be removed')
+ console.log('New and shiny code!')
}
```
#### Marking individual text inside lines
```js "given text"
function demo() {
// Mark any given text inside lines
return 'Multiple matches of the given text are supported';
}
```
#### Regular expressions
```ts /ye[sp]/
console.log('The words yes and yep will be marked.')
```
#### Escaping forward slashes
```sh /\/ho.*\//
echo "Test" > /home/test.txt
```
#### Selecting inline marker types (mark, ins, del)
```js "return true;" ins="inserted" del="deleted"
function demo() {
console.log('These are inserted and deleted marker types');
// The return statement uses the default marker type
return true;
}
```
### Word Wrap
[Word Wrap](https://expressive-code.com/key-features/word-wrap/)
#### Configuring word wrap per block
```js wrap
// Example with wrap
function getLongString() {
return 'This is a very long string that will most probably not fit into the available space unless the container is extremely wide'
}
```
---
```js wrap=false
// Example with wrap=false
function getLongString() {
return 'This is a very long string that will most probably not fit into the available space unless the container is extremely wide'
}
```
#### Configuring indentation of wrapped lines
```js wrap preserveIndent
// Example with preserveIndent (enabled by default)
function getLongString() {
return 'This is a very long string that will most probably not fit into the available space unless the container is extremely wide'
}
```
---
```js wrap preserveIndent=false
// Example with preserveIndent=false
function getLongString() {
return 'This is a very long string that will most probably not fit into the available space unless the container is extremely wide'
}
```
## Collapsible Sections
[Collapsible Sections](https://expressive-code.com/plugins/collapsible-sections/)
```js collapse={1-5, 12-14, 21-24}
// All this boilerplate setup code will be collapsed
import { someBoilerplateEngine } from '@example/some-boilerplate'
import { evenMoreBoilerplate } from '@example/even-more-boilerplate'
const engine = someBoilerplateEngine(evenMoreBoilerplate())
// This part of the code will be visible by default
engine.doSomething(1, 2, 3, calcFn)
function calcFn() {
// You can have multiple collapsed sections
const a = 1
const b = 2
const c = a + b
// This will remain visible
console.log(`Calculation result: ${a} + ${b} = ${c}`)
return c
}
// All this code until the end of the block will be collapsed again
engine.closeConnection()
engine.freeMemory()
engine.shutdown({ reason: 'End of example boilerplate code' })
```
## Line Numbers
[Line Numbers](https://expressive-code.com/plugins/line-numbers/)
### Displaying line numbers per block
```js showLineNumbers
// This code block will show line numbers
console.log('Greetings from line 2!')
console.log('I am on line 3')
```
---
```js showLineNumbers=false
// Line numbers are disabled for this block
console.log('Hello?')
console.log('Sorry, do you know what line I am on?')
```
### Changing the starting line number
```js showLineNumbers startLineNumber=5
console.log('Greetings from line 5!')
console.log('I am on line 6')
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

View File

@@ -1,51 +0,0 @@
---
title: Simple Guides for Fuwari
published: 2024-04-01
description: "How to use this blog template."
image: "./cover.jpeg"
tags: ["Fuwari", "Blogging", "Customization"]
category: Guides
draft: false
---
> Cover image source: [Source](https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=2048/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg)
This blog template is built with [Astro](https://astro.build/). For the things that are not mentioned in this guide, you may find the answers in the [Astro Docs](https://docs.astro.build/).
## Front-matter of Posts
```yaml
---
title: My First Blog Post
published: 2023-09-09
description: This is the first post of my new Astro blog.
image: ./cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
---
```
| Attribute | Description |
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `title` | The title of the post. |
| `published` | The date the post was published. |
| `description` | A short description of the post. Displayed on index page. |
| `image` | The cover image path of the post.<br/>1. Start with `http://` or `https://`: Use web image<br/>2. Start with `/`: For image in `public` dir<br/>3. With none of the prefixes: Relative to the markdown file |
| `tags` | The tags of the post. |
| `category` | The category of the post. |
| `draft` | If this post is still a draft, which won't be displayed. |
## Where to Place the Post Files
Your post files should be placed in `src/content/posts/` directory. You can also create sub-directories to better organize your posts and assets.
```
src/content/posts/
├── post-1.md
└── post-2/
├── cover.png
└── index.md
```

View File

@@ -1,84 +0,0 @@
---
title: Markdown Extended Features
published: 2024-05-01
updated: 2024-11-29
description: 'Read more about Markdown features in Fuwari'
image: ''
tags: [Demo, Example, Markdown, Fuwari]
category: 'Examples'
draft: false
---
## GitHub Repository Cards
You can add dynamic cards that link to GitHub repositories, on page load, the repository information is pulled from the GitHub API.
::github{repo="Fabrizz/MMM-OnSpotify"}
Create a GitHub repository card with the code `::github{repo="<owner>/<repo>"}`.
```markdown
::github{repo="saicaca/fuwari"}
```
## Admonitions
Following types of admonitions are supported: `note` `tip` `important` `warning` `caution`
:::note
Highlights information that users should take into account, even when skimming.
:::
:::tip
Optional information to help a user be more successful.
:::
:::important
Crucial information necessary for users to succeed.
:::
:::warning
Critical content demanding immediate user attention due to potential risks.
:::
:::caution
Negative potential consequences of an action.
:::
### Basic Syntax
```markdown
:::note
Highlights information that users should take into account, even when skimming.
:::
:::tip
Optional information to help a user be more successful.
:::
```
### Custom Titles
The title of the admonition can be customized.
:::note[MY CUSTOM TITLE]
This is a note with a custom title.
:::
```markdown
:::note[MY CUSTOM TITLE]
This is a note with a custom title.
:::
```
### GitHub Syntax
> [!TIP]
> [The GitHub syntax](https://github.com/orgs/community/discussions/16925) is also supported.
```
> [!NOTE]
> The GitHub syntax is also supported.
> [!TIP]
> The GitHub syntax is also supported.
```

View File

@@ -1,175 +0,0 @@
---
title: Markdown Example
published: 2023-10-01
description: A simple example of a Markdown blog post.
tags: [Markdown, Blogging, Demo]
category: Examples
draft: false
---
# An h1 header
Paragraphs are separated by a blank line.
2nd paragraph. _Italic_, **bold**, and `monospace`. Itemized lists
look like:
- this one
- that one
- the other one
Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.
> Block quotes are
> written like so.
>
> They can span multiple paragraphs,
> if you like.
Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all
in chapters 12--14"). Three dots ... will be converted to an ellipsis.
Unicode is supported. ☺
## An h2 header
Here's a numbered list:
1. first item
2. second item
3. third item
Note again how the actual text starts at 4 columns in (4 characters
from the left side). Here's a code sample:
# Let me re-iterate ...
for i in 1 .. 10 { do-something(i) }
As you probably guessed, indented 4 spaces. By the way, instead of
indenting the block, you can use delimited blocks, if you like:
```
define foobar() {
print "Welcome to flavor country!";
}
```
(which makes copying & pasting easier). You can optionally mark the
delimited block for Pandoc to syntax highlight it:
```python
import time
# Quick, count to ten!
for i in range(10):
# (but not *too* quick)
time.sleep(0.5)
print i
```
### An h3 header
Now a nested list:
1. First, get these ingredients:
- carrots
- celery
- lentils
2. Boil some water.
3. Dump everything in the pot and follow
this algorithm:
find wooden spoon
uncover pot
stir
cover pot
balance wooden spoon precariously on pot handle
wait 10 minutes
goto first step (or shut off burner when done)
Do not bump wooden spoon or it will fall.
Notice again how text always lines up on 4-space indents (including
that last line which continues item 3 above).
Here's a link to [a website](http://foo.bar), to a [local
doc](local-doc.html), and to a [section heading in the current
doc](#an-h2-header). Here's a footnote [^1].
[^1]: Footnote text goes here.
Tables can look like this:
size material color
---
9 leather brown
10 hemp canvas natural
11 glass transparent
Table: Shoes, their sizes, and what they're made of
(The above is the caption for the table.) Pandoc also supports
multi-line tables:
---
keyword text
---
red Sunsets, apples, and
other red or reddish
things.
green Leaves, grass, frogs
and other things it's
not easy being.
---
A horizontal rule follows.
---
Here's a definition list:
apples
: Good for making applesauce.
oranges
: Citrus!
tomatoes
: There's no "e" in tomatoe.
Again, text is indented 4 spaces. (Put a blank line between each
term/definition pair to spread things out more.)
Here's a "line block":
| Line one
| Line too
| Line tree
and images can be specified like so:
[//]: # (![example image]&#40;./demo-banner.png "An exemplary image"&#41;)
Inline math equations go in like so: $\omega = d\phi / dt$. Display
math should get its own line and be put in in double-dollarsigns:
$$I = \int \rho R^{2} dV$$
$$
\begin{equation*}
\pi
=3.1415926535
\;8979323846\;2643383279\;5028841971\;6939937510\;5820974944
\;5923078164\;0628620899\;8628034825\;3421170679\;\ldots
\end{equation*}
$$
And note that you can backslash-escape any punctuation characters
which you wish to be displayed literally, ex.: \`foo\`, \*bar\*, etc.

View File

@@ -1,28 +0,0 @@
---
title: Include Video in the Posts
published: 2023-08-01
description: This post demonstrates how to include embedded video in a blog post.
tags: [Example, Video]
category: Examples
draft: false
---
Just copy the embed code from YouTube or other platforms, and paste it in the markdown file.
```yaml
---
title: Include Video in the Post
published: 2023-10-19
// ...
---
<iframe width="100%" height="468" src="https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_" title="YouTube video player" frameborder="0" allowfullscreen></iframe>
```
## YouTube
<iframe width="100%" height="468" src="https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
## Bilibili
<iframe width="100%" height="468" src="//player.bilibili.com/player.html?bvid=BV1fK4y1s7Qf&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>

View File

@@ -0,0 +1,72 @@
---
title: 用户态和内核态
published: 2025-07-18
description: ''
image: ''
tags: [内核态,用户态,操作系统]
category: '操作系统'
draft: false
lang: 'zh-cn'
---
### 用户态User Mode与内核态Kernel Mode总结
#### 1. **基本概念**
* **用户态User Mode**
* 用户程序运行的状态,只能访问受限的资源。
* 无法直接访问硬件设备或内核数据结构。
* 当需要执行敏感操作(如磁盘读写、网络通信)时,需要通过**系统调用**进入内核态。
* 主要用于运行应用程序、库函数等。
* **内核态Kernel Mode**
* 操作系统内核运行的状态,具有最高权限。
* 可以直接访问硬件资源和系统内存。
* 负责进程管理、内存管理、设备管理、文件系统、网络协议栈等核心功能。
* 系统调用的请求会触发用户态切换到内核态执行。
---
#### 2. **用户空间与内核空间**
* **内核空间Kernel Space**
* 存放操作系统内核代码、数据结构。
* 拥有完整的资源访问权限。
* **用户空间User Space**
* 为应用程序分配的内存区域。
* 应用程序无法直接操作硬件或内核数据,需要通过系统调用与内核交互。
---
#### 3. **切换过程**
* 当用户程序调用系统调用接口(如 `open()``read()`)时:
1. 从用户态切换到内核态。
2. 内核完成对应的底层操作(如文件打开、设备读写)。
3. 返回结果,再切换回用户态。
---
#### 4. **区别与特点**
| 特性 | 用户态 | 内核态 |
| ------ | -------- | ------------- |
| 权限 | 受限权限 | 最高权限 |
| 能否访问硬件 | 否 | 是 |
| 主要运行内容 | 应用程序、库函数 | 操作系统内核、驱动程序 |
| 切换开销 | 无 | 需要上下文切换,有一定开销 |
---
#### 5. **优化点**
* **频繁切换用户态与内核态**会带来性能开销(上下文切换 + 权限检查)。
* 因此在系统设计中,会尽量减少不必要的内核调用次数。
---

View File

@@ -0,0 +1,20 @@
---
title: 线程和进程的区别
published: 2025-07-18
description: ''
image: ''
tags: [进程,线程]
category: '操作系统'
draft: false
lang: ''
---
# 线程和进程的区别
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10r4a6l-1.webp)
进程: 是操作系统分配资源的基本单位。每个进程都有自己独立的内存空间,可以看作是一个正在运行的程序实例,进程之间是相互独立的。
线程: 是CPU/任务调度的基本单位,属于进程,一个进程中可以包含多个线程。线程共享进程的内存空间和资源,但每个线程有自己独立的栈和寄存器
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10r4hzg-1.webp)

View File

@@ -0,0 +1,19 @@
---
title: 进程的几种状态
published: 2025-07-18
description: ''
image: ''
tags: [进程状态,操作系统]
category: '操作系统'
draft: false
lang: ''
---
# 进程的几种状态
运行 - 就绪 - 阻塞
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10y3u6x-1.webp)
![image.png](https://blog.meowrain.cn/api/i/2025/07/18/10y44ei-1.webp)

View File

@@ -0,0 +1,78 @@
---
title: 阻塞IO和非阻塞IO同步IO与异步IO
published: 2025-07-18
description: ''
image: ''
tags: [IO,操作系统]
category: '操作系统'
draft: false
lang: ''
---
# 阻塞和非阻塞IO
# 阻塞IO
```jsx
线程发起阻塞IO如read
数据没好被阻塞
线程切换为阻塞态
CPU调度其它进程/线程
IO完成
线程被唤醒进入可运行态
等待被操作系统调度分到CPU
```
当⽤户程序执⾏ read ,线程会被阻塞,⼀直等到内核数据准备好,并把数据从内核缓冲区拷⻉到应⽤程序的缓冲区中,当拷⻉过程完成, read 才会返回。
注意,**阻塞等待的是`内核数据准备好``数据从内核态拷⻉到⽤户态`这两个过程**。
![OS-F06~1](https://blog.meowrain.cn/api/i/2025/07/18/10rukfc-1.webp)
# 非阻塞IO
⾮阻塞的 read 请求在数据未准备好的情况下⽴即返回,可以继续往下执⾏,此时应⽤程序不断轮询内核,直到数据准备好,内核将数据拷⻉到应⽤程序缓冲区, read 调⽤才可以获取到结果。
![OS-771~1](https://blog.meowrain.cn/api/i/2025/07/18/10rskv8-1.webp)
---
# 同步异步IO
![图片.png](https://blog.meowrain.cn/api/i/2025/07/18/10t1y6z-1.webp)
# 同步IO
同步IO只有当IO操作完成以后程序调用才会返回
# 异步IO
I/O请求发出以后立刻返回不管数据是否马上准备好
IO完成的时候操作系统以通知的方式告诉应用。
# 区别
阻塞 vs 非阻塞:关注 进程/线程是否在等待数据就绪。
同步 vs 异步:关注 I/O 操作是否完成(包括数据准备 + 数据拷贝)时,谁来通知结果。
1. 解释
阻塞/非阻塞
阻塞 I/O调用 I/O 时,如果数据还没准备好,进程就会停在那里等(什么都不能做)。
非阻塞 I/O调用 I/O 时,如果数据没准备好,立即返回,不会卡住进程;用户程序可以去做其他事,但需要主动检查数据是否准备好。
同步/异步
同步 I/O用户发起 I/O 请求后,必须等 I/O 完成(数据准备 + 数据复制)才能继续,比如 read() 等数据拷贝完才返回。
异步 I/O用户发起 I/O 请求后,立即返回;等 I/O 完成(数据准备 + 拷贝)时,内核主动通知用户(回调或信号)。

View File

@@ -0,0 +1,31 @@
---
title: 零拷贝技术
published: 2025-07-18
description: ''
image: ''
tags: [零拷贝,操作系统,IO]
category: '操作系统'
draft: false
lang: ''
---
# 零拷贝技术
![OS-1E5~1](https://blog.meowrain.cn/api/i/2025/07/18/10vy5ev-1.webp)
![图片.png](https://blog.meowrain.cn/api/i/2025/07/18/10vz716-1.webp)
# sendfile原理
sendfile()系统调用能让内核直接把文件描述符的内容传送到另外一个文件描述符。
数据绕过了用户空间。
![图片.png](https://blog.meowrain.cn/api/i/2025/07/18/10vzeqy-1.webp)
# mmap原理
把文件或者其他对象映射到进程虚拟地址空间,它的零拷贝主要体现在数据共享和懒加载上。
![图片.png](https://blog.meowrain.cn/api/i/2025/07/18/10vzg41-1.webp)
![图片.png](https://blog.meowrain.cn/api/i/2025/07/18/10vzxk4-1.webp)

View File

@@ -1,9 +1,39 @@
# About <p align="center">
This is the demo site for [Fuwari](https://github.com/saicaca/fuwari). <img src="https://github.com/user-attachments/assets/3bd433d4-4e26-4a94-a271-91ee0dfd4fe2" alt="Banner Image" width="60%"/>
</p>
::github{repo="saicaca/fuwari"}
> ### Sources of images used in this site <h1 align="center">Hi there! 👋 I'm MeowRain</h1>
> - [Unsplash](https://unsplash.com/) <p align="center">A college student exploring the world of code — especially in Go! 🐹</p>
> - [星と少女](https://www.pixiv.net/artworks/108916539) by [Stella](https://www.pixiv.net/users/93273965)
> - [Rabbit - v1.4 Showcase](https://civitai.com/posts/586908) by [Rabbit_YourMajesty](https://civitai.com/user/Rabbit_YourMajesty) ---
### 🐾 About Me
I'm all about keeping things **simple** and **joyful** 😊❤️
From diving deep into backend systems with Go, to tinkering with tools and tech, I believe learning should always be fun and meaningful.
---
### 📖 My Blog
Wanna see what I'm working on or thinking about?
👉 [**MeowRain's Blog**](https://meowrain.cn)
---
### 🛠️ Tech Stack & Tools
- **Languages:** Go (主力), JavaScript, Python,Java,
- **Frameworks:** Go-zero, React, Vue
- **Tools:** Git, Docker, VSCode, Terminal (with ❤️), Tmux
- **Cloud & DevOps:** Tailscale, Nginx, systemd, etc.
<img src="https://img.shields.io/badge/Golang-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white"/>
<img src="https://img.shields.io/badge/VSCode-007ACC?style=for-the-badge&logo=visual-studio-code&logoColor=white"/>
<img src="https://img.shields.io/badge/Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white"/>
---
### ☁️ Let's Build Cool Stuff
I love building things that are clean, efficient, and fun to use.
If you're into Go, backend architecture, or cool CLI tools — we're already friends. ✨
---