【java入门到放弃】需要背诵

基础

方法重载:同一个类中,方法名相同,参数不同

方法覆盖、方法重写:子类重写父类方法,实现不同逻辑

多态:有继承关系的父子类,子类重写父类方法,父类引用指向子类对象,通过父类引用调用子类方法,实现运行时动态绑定。

抽象类不能new,只能通过子类实现,抽象类可以多级继承,抽象类只能单继承(extends)

接口可以多继承,有常量,抽象方法,默认方法,静态方法(implements)

StringBuffer(线程安全)、StringBuilder负责"高效可变字符串"

为了 解决0 的表示不唯一和方便计算机运算等问题,所以用补码表示。-0也就是-128。

匿名内部类

父类或者接口类型 变量名 = new 父类或者接口(){

// 方法重写

@Override

public void method() {

// 执行语句

}

};

//调用实现(重写)后的方法

变量名.method();

本质是一个子类对象

Lambda

有且只有一个抽象方法的接口,就是函数式接口。该接口中,也允许有其他的默认方法和静态方法。

使用Lambda函数替代某些匿名内部类对象,Lambda表达式只能替代函数式接口的匿名内部类

函数式接口,可以用@FunctionalInterface注解

参数类型全部可以省略不写。

如果只有一个参数,参数类型省略的同时"()"也可以省略,但多个参数不能省略"()"

如果Lambda表达式中只有一行代码,大括号可以不写,同时要省略分号";"如果这行代码是return语句,也必须去掉return。

类名::静态方法。Lambda表达式里只是调用一个静态方法,并且"→"前后参数的形式一致,就可以使用静态方法引用

对象名::实例方法。Lambda表达式里只是通过对象名称调用一个实例方法,并且"→"前后参数的形式一致,就可以使用实例方法引用

特定类的名称::方法。Lambda表达式里只是调用一个特定类型的实例方法,并且前面参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型的方法引用。

类名::new。Lambda表达式里只是在创建对象,并且"→"前后参数情况一致,就可以使用构造器引用。

JVM


JVM 类加载器是负责把 .class 字节码文件加载到 JVM 中,并完成验证、准备、解析等过程的组件。加载连接(验证准备解析)初始化

启动类加载器:加载 Java 核心类库; 扩展类加载器:默认加载Java安装目录/jre/lib/ext下的类文件; 应用类加载器:加载自己写的类。

双亲委派机制指的是:当一个类加载器接收到加载类的任务时,会自底向上查找是否加载过,再由顶向下进行加载。

打破双亲委派机制,主要是为了满足"框架自定义加载类 + 类隔离 + 热部署"等需求

类加载器把类"加载进来",执行引擎负责"真正执行代码"

解释器:一行一行解释执行字节码

JIT 编译器:把"热点代码"编译成机器码,直接执行

垃圾回收器(GC):自动回收不再使用的对象,主要回收堆,方法区也回收

符号引用就是一种"用名字表示的引用",在编译期存在,运行时会被解析成真正的内存地址(直接引用)。

Java虚拟机栈的栈帧中主要包含四方面的内容:

  • 局部变量表,局部变量表的作用是在运行过程中存放所有的局部变量
  • 操作数栈,用于计算的"临时栈"
  • 动态链接,找到方法调用的"真正地址",方法在编译时是"符号引用",运行时需要转换为"实际地址"
  • 方法返回地址,方法执行完后,返回到哪里继续执行

本地方法栈存储的是native本地方法的栈帧。

程序计数器(Program Counter Register)也叫PC寄存器,每个线程会通过程序计数器记录当前要执行的的字节码指令的地址。

方法区是存放基础信息的位置,线程共享,主要包含三部分内容:

  • 类的元信息,保存了所有类的基本信息(类名、包名、父类、实现的接口、访问修饰符(public / abstract 等)、字段信息、方法信息)
  • 运行时常量池,保存了字节码文件中的常量池内容(字面量(literal),类/方法的符号引用),字符串会在"运行时常量池"里有一份"符号记录"
  • 字符串常量池,保存了字符串常量
  • 静态变量

JDK7 之前

  • 方法区实现:永久代(PermGen)

JDK8 之后

  • 改为:元空间(Metaspace)
项目 永久代 元空间
内存 JVM内存 本地内存
是否易OOM 容易 不容易

堆(Heap)是 JVM 中用来存放"对象实例"和数组的内存区域,是垃圾回收的主要区域。

字符串常量池也在堆中(JDK7之后)

复制代码
堆(Heap)
 ├── 新生代(Young)
 │     ├── Eden
 │     ├── Survivor 0
 │     └── Survivor 1
 │
 └── 老年代(Old)

对象一般先在新生代创建,经过多次垃圾回收仍存活的对象,会被晋升到老年代。

第一次GC:Eden → S0(age=1)

第二次GC:S0 → S1(age=2)

第三次GC:S1 → S0(age=3)

年龄达到阈值(默认15),对象进入:老年代(Old)

大对象、 Survivor 放不下会直接进老年代

新生代内部比例:Eden : S0 : S1 = 8 : 1 : 1

默认比例大概是:新生代 : 老年代 ≈ 1 : 2

基础

Java 中包装类在一定范围内(如 Integer 的 -128 到 127)会使用缓存机制,在堆中创建。

包装类 缓存范围
Integer -128 ~ 127
Byte -128 ~ 127(全部)
Short -128 ~ 127
Long -128 ~ 127
Character 0 ~ 127
Boolean true / false(2个)
Float ❌ 无缓存
Double ❌ 无缓存

集合、泛型、需要null的场景,必须使用包装类。

集合

数组的长度是不可变的,集合的长度是可变的

数组可以存基本数据类型和引用数据类型

集合只能存引用数据类型

Java 集合是用来存储多个对象的容器,主要分为 Collection 和 Map 两大体系。

List(有序、可重复) Set(无序、不可重复)

哈希(Hash)是一种将任意长度的输入(如字符串、数字、文件等)通过哈希函数转换为固定长度输出(通常是一串数字和字母)的数据结构或技术。

哈希表(Hash Table,也叫散列表)是一种基于"键-值"存储的高效数据结构。它的核心思想是通过哈希函数,将键(Key)直接映射到一个固定数组的某个位置,从而实现接近常数时间的快速访问。

"哈希表"是Map实现方式之一。

哈希表是一种通过哈希函数将 Key 映射到数组下标,从而实现快速存取的数据结构。

key → hashCode → index。再把value和key,一起绑定到index。

在 Java JDK 8+ 的 HashMap 中:

参数 默认值

初始容量 16

负载因子 0.75,大于12时,才扩容

链表 → 树 8,大于8时,才变红黑树,还要容量不小于64

树 → 链表 6,小于6时,才变链表

树化最小容量 64,容量不小于64,链表才会开始变树

扩容规则就是:容量 × 2(翻倍)

HashMap 扩容时会根据新容量重新确定每个元素的存放位置;

ConcurrentHashMap 线程安全

ArrayList 每次扩容为 1.5 倍

ArrayList 底层是数组,支持随机访问(O(1)),查询快

中间插入需要整体移动元素,ArrayList 插入慢

异常

为什么要分错误和异常?

Java 区分 Error 和 Exception,是为了明确:

哪些问题 程序 可以处理,哪些问题必须放弃。

OutOfMemoryError、StackOverflow。处理不了

ErrorIOException、NullPointerException。可以通过 代码 处理

为什么要分成运行时异常和编译时异常?

编译时异常本身不是在编译阶段发生的,只是编译器在提醒你"调用这个方法可能会抛出异常,你必须处理它"

也就是java在 设计 时,提示 开发者 代码可能会出现这些错误。

Java 没有把所有异常都设计成编译时异常(Checked Exception),是为了在 程序健壮性 和 开发便利性 之间做平衡

处理

捕获异常(try-catch)

声明抛出异常(throws)

throw就是直接抛出异常。

Java 官方库的方法需要我们处理异常,是因为它们在设计时就声明了 throws,而 Java 异常本身是在底层代码里通过 throw 抛出来的。

理解

异常是每个请求独立发生的,本次请求出现异常只影响当前请求线程,下次请求不会受到影响。

如果spring没有处理异常,就会返回500的响应

finally 中 return 会 覆盖 try / catch 的 return

System.exit() 会立即终止整个 JVM,后续代码不再执行(除非被阻止),一般用于程序级别的退出。

git

1、有分支记录的提交方法

本地分支gong上修改代码后,提交到远程仓库master主分支

git add

git commit -m

git checkout master

git pull origin master

git merge gong

git push origin master

2、扩展命令

本地修改后,个别不提交

git stash

其他提交完后,恢复那些文件

git stash pop

提交时忽略vue的严格模式

git commit -m "bug修复" --no-verify

3、没有分支记录的提交,保证提交记录是一条直线

  1. 在 gong 分支提交

    git add

    git commit -m "修改"

  2. 确保 master 最新

    git checkout master

    git pull origin master

  3. 回到 gong 分支,把修改叠加到最新 master

    git checkout gong

    git rebase master # 整理提交顺序,历史直线化

    3.1.如果发生冲突,修改文件

    git add <冲突文件>

    git rebase --continue

  4. 切回 master,快进合并

    git checkout master

    git merge --ff-only gong # 不会生成 merge commit

    4.1.如果合并失败,重新rebase

    git pull origin master

    git checkout gong

    git rebase master

  5. 推送 master

    git push origin master

    5.1. 如果推送失败,重新拉取

    git pull --rebase origin master

    5.1.1 如果拉取失败,解决冲突然后继续

    git add <冲突文件>

    git rebase --continue

    git push origin master

网络

防火墙既可以是硬件,也可以是软件,本质上是一种安全功能/技术,而不是只能对应某一种形态。

NAT(Network Address Translation,网络地址转换),把私有 IP 转换成公网 IP(或反之)

核心目的:解决 IPv4 地址不够用,隐藏内部网络结构

防火墙 / 路由器维护一张 NAT 映射表

VPN(Virtual Private Network,虚拟专用网络), 在不安全的公网中建立"加密隧道"

核心目的:数据加密,身份认证,安全访问内网资源

用户 →【加密】→ 公网 →【解密】→ 内网

1 bit = 最小信息单位(比特)(位)

1 Byte = 8 bit (Byte,字节)

1 KB = 1024 B

1 MB = 1024 KB

1 GB = 1024 MB

1 TB = 1024 GB

1 Kbps = 1000 bit/s

1 Mbps = 1000 Kbps = 1,000,000 bit/s

1 Gbps = 1000 Mbps = 1,000,000,000 bit/s

1 Tbps = 1000 Gbps = 1,000,000,000,000 bit/s

兆:指带宽速度,单位是 Mbps(Megabits per second)。1 Mbps = 每秒传输 1 兆比特(Megabit)

存储速度 ↔ 网络速度:一个以字节为基准,一个以比特为基准

1 B/s = 8 bit/s

1 MB/s = 8 Mbps

1 GB/s = 8 Gbps

DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)一句话解释:DHCP 负责自动给网络里的设备分配 IP 地址和网络参数,让设备"开机就能上网",不需要手动设置 IP。

手机使用运营商流量上网时:大部分情况下拿到的是 私网 IP,通过运营商的 NAT 映射到公网 IP

这里私网IP和公网IP都不是固定的。

光猫:接光纤 + 转换信号

路由器:分发 WiFi + 管理网络

光猫都会被分配一个 IP,但普通家庭用户通常是动态 IP(公网或私网 NAT 映射),固定 IP 一般需要申请企业或高端套餐。

现在很多光猫确实会集成路由器功能

在家庭网络中,设备常见有几种工作模式:

路由模式是最常用的,设备负责拨号上网、分配 IP 和网络转发,用户设备可以直接上网;

桥接模式则让光猫只负责光信号转换,把拨号和路由功能交给下级路由器,常用于避免"双 NAT"以提升网络稳定性;

AP 模式主要用于扩展有线网络下的无线覆盖,本身不负责拨号和分配 IP,而是把有线网络变成 WiFi;

中继模式则是通过无线方式接收并转发信号,用来扩大覆盖范围,但会带来一定的速度和稳定性下降。这些模式本质上是让同一设备在网络中扮演不同角色。

路由器的两个口就是"外网口和内网口":

  • WAN口:路由器的出口,连互联网或上级网络
  • LAN口:路由器的局域网接口,连你的电脑、手机和下级网络
  • WAN 口动态即可,LAN 口一般固定,作为子网网关

LAN口一般固定

  1. LAN 口 是路由器提供给内网设备的接口,也就是子网网关。
  2. 固定 IP 的原因:
    • LAN 口的 IP 是子网内设备访问路由器的地址(网关地址)
    • DHCP 服务器、静态路由、端口映射、子网管理都依赖这个固定网关

当你把电脑用网线直接接到路由器的 LAN 口 时,在 光纤通信 的家庭网络中,路由器一般会通过 DHCP 服务 自动给你的电脑分配一个局域网 IP。

普通交换机是没有 DHCP 功能的

数据经过普通交换机时,IP 不会改变

IO多路复用

fd(File Descriptor,文件描述符)

fd_set是一个"文件描述符集合"的位图数据结构,用来告诉操作系统:我关心哪些 fd 的 IO 状态(可读 / 可写 / 异常)。

fd_set是在select模式下使用的

pollfd 是 Linux / POSIX 系统中 poll() 系统调用的核心数据结构,用来告诉内核你想监听哪些文件描述符,以及关心哪些事件。

它类似于 select的 fd_set,但比 select 更灵活,而且没有 FD_SETSIZE 限制。

内核是"管理者",内存是"被管理的资源"。

内核本身也住在内存里,但它住在"受保护的那一部分"。

内核是"程序",内核空间/用户空间是"内存区域(地址空间)"。

内核空间是操作系统为内核及其相关数据保留的内存地址区域。

用户空间是普通应用程序运行和使用的内存地址区域。

同一个线程,在不同时间、不同权限级别,访问不同空间。

IO多路复用技术是操作系统的技术,redis和nginx等一些中间件,使用IO多路复用技术就是去调用操作系统所提供的接口。

select 模式是 IO 多路复用的一种实现方式,它允许一个线程同时监控多个文件描述符(fd)的读、写、异常状态,从而实现单线程处理多个 IO 连接的能力。

工作流程:(指的是用户程序调用操作系统实现的select模式IO多路复用)

1、用户态准备 fd_set

2、调用 select 系统调用

3、内核处理

4、用户态处理就绪 fd

在大多数 Unix / Linux系统中:一个 fd_set 最多只能同时监听 1024 个文件描述符

fd_set不管有多少数据,都是全部扫描一遍,扫描1024个

把线程挂起 → 被事件或超时唤醒后,再重新扫描一整遍。

唤醒时,不会传递fd,所以需要重新扫描

select 返回用户态之后,内核空间里"不会保留任何 fd_set"。

下一次调用 select,不管上次有没有就绪 fd,用户都必须重新把"完整的 fd_set"传给内核。

内核把"就绪结果"写回 fd_set 后返回;

用户态必须再从 0 ~ maxfd 自己遍历一遍,才能找出哪些 fd 就绪。

poll 是 IO 多路复用的一种实现方式,用数组而不是位图(fd_set)来描述要监听的 fd,消除了 fd 数量上限,但内核处理模型仍是 O(n)。

select 的核心问题有 3 个:

1、fd 数量有上限;FD_SETSIZE = 1024

2、fd_set 是位图fd 必须是连续编号

3、每次都要全量扫描 fd

poll 的目标是:

解决 1 和 2,但不彻底解决 3

epoll

1、用户态准备 pollfd 数组

2、用户态poll() 系统调用

3、内核态copy_from_user 拷贝 pollfd 数组

4、遍历 pollfd,调用每个 fd 的 poll 回调

5、如果无就绪 fd, 把线程挂起(TASK_INTERRUPTIBLE)

6、IO 中断到来, 唤醒等待队列

7、再次扫描 pollfd

8、填充 revents

9、copy_to_user 返回结果

10、用户态遍历 fds[i].revents,处理就绪 fd

selete和poll就是一有返回,就把内核态的fd_set或 pollfd 数组删了,epoll是注册了,就一直存在,需要用户态手动删除,才能删除。epoll是一个一个的注册fd。

netty

Bootstrap / ServerBootstrap 启动器 / 服务端启动器

EventLoop / EventLoopGroup 事件循环/事件循环组

Channel 通道

ChannelPipeline 管道

ChannelHandler 处理器

ChannelHandlerContext 处理器上下文

ChannelFuture 异步结果

ByteBuf 字节缓冲区

Bootstrap / ServerBootstrap 负责将 Channel、EventLoopGroup、ChannelPipeline、ChannelHandler 以及相关配置组合并启动。

EventLoop 是负责 IO 事件和任务执行的单线程执行器,一个 EventLoop 可以管理多个 Channel,而一个 Channel 在生命周期内只绑定一个 EventLoop。EventLoopGroup 是管理多个 EventLoop 的线程池。

每个 Channel 都绑定一个 ChannelPipeline,Pipeline 由多个 ChannelHandlerContext 组成,每个 ChannelHandlerContext 对应一个 ChannelHandler。

当 IO 事件在 Channel 上发生时,事件会沿着 ChannelPipeline 传播,由其中的 ChannelHandler 依次进行处理或拦截。

ChannelHandlerContext 表示当前 Handler 在 Pipeline 中的操作入口和事件传播起点,可用于继续传递事件、写数据以及访问 Channel 和 EventLoop。

一个 Channel 可以产生多个 ChannelFuture,每个 ChannelFuture 只对应一次具体的异步 Channel 操作,用于表示该操作的完成状态(未完成 / 成功 / 失败)。

ByteBuf 是 Netty 中用于网络 IO 的高性能字节容器,负责数据存储、读写指针和内存管理,通常在 Handler 中被读取和处理,用于替代 ByteBuffer。

网络

OSI 七层模型:物理层、链路层、网络层、传输层、会话层、表示层、应用层

TCP/IP 四层模型:链路层、网络层、传输层、应用层

七层是理论模型,四层是实际实现。

链路层:Ethernet, WiFi, PPP

网络层:IP, ICMP, ARP

传输层:TCP, UDP

应用层:HTTP, HTTPS, FTP, DNS, SMTP, POP3

TCP/IP 不是单一协议,而是一整套协议的统称,是一个协议族。

TCP协议是解决传输,IP协议是解决互联。

链路层最核心的协议就是 Ethernet(以太网)。

ARP 全称是:Address Resolution Protocol(地址解析协议)

ARP 表就是一个 缓存表,记录了 IP 和 MAC 的对应关系。

NAT(Network Address Translation,网络地址转换)是一个在路由器或防火墙上进行 IP 地址转换的技术,本质作用是:让局域网里的多台设备通过一个公网 IP 上网,同时隐藏内部网络结构,提高安全性。

NAT 只改 IP

NAPT 改 IP + 端口

路由表的作用:决定数据包"下一跳发给谁"

由上一节可知,,源ip和目标ip都会变。

IP协议去Ethernet协议:太大了就分片,网络层封装ip报头,数据链路层封装mac帧报头。

Ethernet协议去IP协议:数据链路层去掉mac帧报头,网络层去掉ip报头,之前有分片就需要组装。

TCP全称"传输控制协议"(Transmission Control Protocol),是如今互联网应用最为广泛的传输层协议

标志位 缩写 含义 / 功能
URG Urgent 紧急指针是否有效,指示有紧急数据
ACK Acknowledgment 确认号是否有效,表示这是确认报文
PSH Push 提示接收端应用立即读数据,不用缓冲太久
RST Reset 重置连接,请求对方重新建立连接
SYN Synchronize 请求建立连接(同步序号)
FIN Finish 通知本端要关闭连接

TCP存在接收缓冲区和发送缓冲区

TCP报头中就有了16位窗口大小,这个16位窗口大小中填的是自身接收缓冲区中剩余空间的大小,即当前主机接收数据的能力

接收端在对发送端发来的数据进行响应时,就可以通过16位窗口大小告知发送端自己当前接收缓冲区剩余空间的大小,此时发送端就可以根据这个窗口大小字段来调整自己发送数据的速度

三次握手

第一次客户端发送 SYN 报文(带初始序号 x),表示请求建立连接;

第二次服务端收到后回复 SYN+ACK(确认 x+1,并发送自己的初始序号 y),表示同意连接并告知自己的序号;

第三次客户端再发送 ACK(确认 y+1),表示已收到服务端信息,至此双方确认彼此收发能力正常,连接建立完成。

四次挥手

第一次主动关闭方发送 FIN(SEQ=x),表示"我这边数据发完了";

第二次被动方回复 ACK(ACK=x+1),表示"我知道你要关闭了";

第三次被动方在数据发送完后再发送 FIN(SEQ=y),表示"我这边也发完了";

第四次主动方回复 ACK(ACK=y+1),表示"确认关闭",随后进入 TIME-WAIT,最终双方连接彻底关闭。

为什么不三次挥手

服务器收到客户端的 FIN 报文时,内核会马上回一个 ACK 应答报文,但是服务端应用程序可能还有数据要发送,所以并不能马上发送 FIN 报文,而是将发送 FIN 报文的控制权交给服务端应用程序:

如果服务端应用程序有数据要发送的话,就发完数据后,才调用关闭连接的函数;

如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数,

HTTP(Hyper Text Transfer Protocol)协议又被称为做超文本传输协议,是一种简单的请求-响应协议,HTTP通常运行在TCP之上

大部分新 Spring Boot 项目:

  • 默认 HTTP/1.1
  • 简单、兼容性好、易部署
  • 适合小型或内部系统,不需要额外配置

状态码,三位数字,例如:

  • 2xx 成功(200 OK)
  • 3xx 重定向(301 Moved Permanently)
  • 4xx 客户端错误(404 Not Found)
  • 5xx 服务器错误(500 Internal Server Error)

HTTPS协议

加密方式

服务器给客户端传非对称加密传公钥,客户端使用公钥加密之后对称加密的密钥,然后传给服务器,如此一来,服务器和客户端就都有了对称加密的密钥。

TLS 证书

证书里的公钥是明文的(不加密),核心安全点不在"保密",而在"防篡改"

数字签名 = 私钥签名,公钥验证

浏览器(或操作系统)内置了一批"受信任的 CA 公钥"

线程

继承 Thread 类

简单但是有java单继承的局限性

实现 Runnable 接口

不能接收返回值

Callable + FutureTask

NEW(新建)、 RUNNABLE(可运行)、 BLOCKED(阻塞)、 WAITING(无限等待)、TIMED_WAITING(超时等待)、 TERMINATED(终止)、

start

sleep

sleep是 Java 中 Thread 类的一个静态方法:Thread.sleep(long millis)

不释放锁!!!

join

实例方法。

一个线程里加入另一个线程,自身线程先停止运行。

java 复制代码
t.join();//无限等待
t.join(1000); // 最多等1秒

wait和notify

Object类中有三个方法: wait()、notify()、notifyAll

三个核心点:

任何对象中都一定有这三个方法

只有对象作为锁对象的时候,才可以调用

只有在同步的代码块中,才可以调用

其他情况下,调用一个对象的这三个方法,都会报错!

obj.wait(); 当前线程从Runnable变成Waiting

obj.notify();随机唤醒一个等待obj锁对象的线程,从Waiting变成Runnable

obj.notifyAll();所以等待obj锁对象的线程,从Waiting变成Blocked,随机一个再变成Runnable

obj.wait(1000); 当前线程从Runnable变成Timed_Waiting,时间到了后,变成blocked

interrupt

把false改成true

线程类Thread中的 interrupt 方法:非静态

线程A中,调用了线程B的 interrupt 方法,而此时线程B处于阻塞状态,那么此时 sleep 方法或者 join 方法 或者 wait 方法就会抛出被打断的异常

interrupt 方法是通过改变线程对象中的一个标识的值(true|false),来达到打断阻塞状态的效果。

一个线程在阻塞状态下,会时刻监测这个标识的值是不是true,如果一旦发现这个值变为true,那么就抛出异常结束阻塞状态,并再把这个值改为false。

interrupt 方法中其实是调用了 interrupt0 这个本地方法

如果变成了true 再sleep,会立即抛异常。

java 复制代码
try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    //此时:线程变阻塞或可运行,标识符变false。
    //中断标志重新变成 true
    Thread.currentThread().interrupt(); 
}

Thread.sleep(10000); // 再次 sleep,如果上面中断标志重新变成 true,这时就会抛异常

//Thread.currentThread().interrupt()目的是:
//catch InterruptedException 后,恢复中断标志的目的,是把这次中断"传递"给上层或后续逻辑,让线程能够正确响应,而不是吞掉它。

isInterrupted

返回这个"打断标识"值

线程类Thread中的 isInterrupted 方法:非静态

这个非静态方法,只是返回这个"打断标识"值,并且不会对这个值进行清除(true-

false),因为所传参数ClearInterrupted的值为false

interrupted

返回这个"打断标识"值,并true->false

线程类Thread中的 interrupted 方法:静态

这个静态方法,返回这个"打断标识"值,并且会对这个值进行清除(true->false),因为所传参数ClearInterrupted的值为true

线程池

方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。

方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。

java 复制代码
ExecutorService pool = new ThreadPoolExecutor(
3,//指定线程池的核心线程的数量
5,//指定线程池的最大线程数量
10, //指定临时线程的存活时间
TimeUnit.SECONDS, 
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);

// 2、使用线程池处理Runnable任务
Runnable target = new MyRunnable();
pool.execute(target); 

 // 2、使用线程池处理Callable任务!
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));    
try {
    System.out.println(f1.get());
    System.out.println(f2.get());
} catch (Exception e) {
    e.printStackTrace();
}

// 3、关闭线程池 :一般不关闭线程池。
// pool.shutdown(); // 等所有任务执行完毕后再关闭线程池!
//pool.shutdownNow(); // 立即关闭,不管任务是否执行完毕!

参数一:corePoolSize : 指定线程池的核心线程的数量。

参数二:maximumPoolSize:指定线程池的最大线程数量。

参数三:keepAliveTime :指定临时线程的存活时间。

参数四:unit:指定临时线程存活的时间单位(秒、分、时、天)

TimeUnit.SECONDS(MINUTES,HOURS,DAYS等)

参数五:workQueue:指定线程池的任务队列。

new ArrayBlockingQueue<>(3),

LinkedBlockingQueue PriorityBlockingQueue DelayQueue等

参数六:threadFactory:指定线程池的线程工厂。

Executors.defaultThreadFactory(),

如果不指定ThreadFactory,ThreadPoolExecutor将使用默认的线程工厂Executors.defaultThreadFactory()。这个默认工厂创建的线程将继承当前线程组,拥有默认的优先级,并且不是守护线程。

参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理

new ThreadPoolExecutor.CallerRunsPolicy()

Java提供了几种预定义的拒绝策略,包括:

复制代码
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。是默认的策略
ThreadPoolExecutor.CallerRunsPolicy:由主线程负责调用任务的run()方法从而绕过线程池直接执行
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常,这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃最旧的任务(即最早被添加到工作队列中的任务),然后尝试重新提交被拒绝的任务。

什么时候开始创建临时线程?

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

什么时候会拒绝新任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。

通过Executors创建线程池

是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。

public static ExecutorService newFixedThreadPool(int nThreads)

创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。

public static ExecutorService newSingleThreadExecutor()

创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。

public static ExecutorService newCachedThreadPool()

线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。

注意 :这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。

大型并发系统环境中使用Executors如果不注意可能会出现系统风险。

JUC

synchronized 是 Java 中最经典的 线程同步关键字,用于解决多线程并发访问共享资源时的数据安全问题。

可重入锁、非公平锁、自动释放锁、阻塞锁

锁升级过程:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

ReentrantLock

可重入锁、支持公平锁、手动释放锁、支持中断锁、支持非阻塞锁

volatile

volatile 的作用:保证"可见性"和"有序性",但不保证"原子性"

AtomicInteger

提供原子操作的整型类

可以在 多线程下安全地修改整数

不需要 synchronized 或显式锁

属于 java.util.concurrent.atomic 包

AtomicInteger 不是真的"加锁",而是利用 CAS(Compare-And-Swap) 实现原子性

Lock 是一个接口,位于 java.util.concurrent.locks包中。

ReentrantLock 是一个类,它实现了 Lock接口。

ReadWriteLock 是另一个接口,它也位于 java.util.concurrent.locks包中,但它和 Lock 接口是并列的,没有继承关系。

LongAdder

适合:高并发计数、统计请求数、QPS 统计、

不适合:需要"实时精确值"、余额计算、

LongAdder 通过将单点更新分散到多个变量(cell)上,降低 CAS 冲突,在高并发场景下比 AtomicLong 性能更高,但读取结果时是最终一致而非实时精确。

LockSupport

LockSupport 是线程阻塞与唤醒的"原子级工具",AQS 等高级同步器正是基于它构建的。

LockSupport.unpark(thread); // 发放许可

LockSupport.park(); // 消耗许可

LockSupport 的许可是"单次许可",不是计数许可:要么有(1),要么没有(0)。

AQS

在 Java 里:AQS(AbstractQueuedSynchronizer)是一个用来构建锁和同步器的"基础框架"。

中文 一般叫:抽象队列同步器

既是一种思想,也是一个框架,但更准确说: 是一个"基于队列的同步框架"

public abstract class AbstractQueuedSynchronizer

核心思想

用 state 表示"资源状态",0:资源可用,其他表示加锁多少次。用 FIFO 队列管理等待线程,先进先出(FIFO)。 CAS(抢资源),阻塞线程:LockSupport.park(); 唤醒线程:LockSupport.unpark(thread);

AQS 的公平性,不取决于"是否进入队列",而取决于"是否允许新线程插队"。

线程轮流执行

方式1:wait / notify

一个对象用来加锁,一个boolean类型的flag,两个线程里两个循环,获取锁,然后判断flag,不是就wait,是就执行,然后修改flag,再notifyAll。

方式2:ReentrantLock + Condition

使用一个 ReentrantLock 作为锁,通过创建两个 Condition 分别作为两个线程的等待队列,再配合一个 boolean 类型的 flag 控制执行顺序;两个线程各自循环获取锁后判断 flag,不满足条件就调用对应的 await 进入各自的等待队列并释放锁,满足条件则执行任务,然后修改 flag,并通过 signal 唤醒对方线程所在的 Condition 队列,最后释放锁,从而实现线程交替执行。

方式3:LockSupport

使用 LockSupport提供的 park/unpark 机制,通过两个线程之间直接"发放许可"来控制执行顺序;两个线程各自循环执行,线程1先打印,然后调用 unpark 唤醒线程2,并调用 park 挂起自己;线程2先 park 等待,被唤醒后执行打印,再通过 unpark 唤醒线程1,如此往复。相比 wait/notify 和 Condition,不需要额外的锁或条件变量,而是通过"许可"实现线程的精确唤醒和交替执行。

登录

Session

Session 默认存储在服务器内存中(Servlet 容器)

存在问题:重启丢失、分布式问题

可以使用Redis存 Session 来解决

同源策略是指,若页面的源和页面运行过程中加载的源不一致时,出于安全考虑,浏览器会对跨域的资源访问进行一些限制

源:协议 + 域名 + 端口

Ajax 跨域时:请求能发出去,后端能收到并处理,也能返回结果,但浏览器不让 前端 代码拿到响应

解决

CORS

本质:服务器在响应头里告诉浏览器:这个请求可以跨域访问。

关键响应头举例:Access-Control-Allow- Origin : http://localhost:3000

java 复制代码
//springboot后端配置
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //能用 * 的时候别带 cookie,要带 cookie 就必须写具体域名。"
        registry.addMapping("/**") //对哪些接口启用跨域规则
                .allowedOrigins("*")  // 允许所有
                .allowedMethods("*")  // GET POST 等
                .allowedHeaders("*");  //允许客户端带哪些请求头
    }
}

代理

对于前端开发而言,大部分的跨域问题,都是通过代理解决的

代理适用的场景是:生产环境不发生跨域,但开发环境发生跨域

复制代码
// vue 的开发服务器代理配置
// vue.config.js
module.exports = {
	devServer: { // 配置开发服务器
		proxy: { // 配置代理
			"/api": { // 若请求路径以 /api 开头
				target: "http://dev.taobao.com", // 将其转发到 http://dev.taobao.com
			},
		},
	},
};

JSONP

在CORS出现之前,人们想了一种奇妙的办法来实现跨域,这就是JSONP。

要实现JSONP,需要浏览器和服务器来一个天衣无缝的绝妙配合。

JSONP的做法是:当需要跨域请求时,不使用AJAX,转而生成一个 script 元素去请求服务器,由于浏览器并不阻止script元素的请求,这样请求可以到达服务器。服务器拿到请求后,响应一段JS代码,这段代码实际上是一个函数调用,调用的是客户端预先生成好的函数,并把浏览器需要的数据作为参数传递到函数中,从而间接的把数据传递给客户端

JSONP有着明显的缺点,即其只能支持 GET 请求

Nginx 本身不是专门解决跨域的工具,但可以通过反向代理将跨域请求转为同源请求,从而绕过浏览器的同源策略限制。此外,也可以通过配置响应头实现 CORS 来支持跨域访问。

cookie传到后端会解析成Map< String , String>

Session 在使用上类似 Map,通过 key-value 存储数据

JWT是无状态的,支持分布式

Session要支持分布式,需要存到redis里

Spring

@Configuration

Spring 的配置类标记注解,用来定义 Bean,并让 Spring 容器管理这些 Bean。

要配合@Bean使用,@Bean→ 告诉 Spring 这个方法的返回值要被管理为 Bean

如果没有@Bean

Spring 扫描它后,会把 配置类本身注册为一个 Bean

结论:不会报错,Spring 可以正常启动,只是这个配置类本身没啥用

@Transactional

java 复制代码
//1、自调用失效

this.method(); //  事务不生效,因为 Spring 用的是 AOP 代理

//通过 AOP 代理获取当前 Bean,使事物生效
 ((MyService) AopContext.currentProxy()).methodA(); //事务生效
//要求在配置类中开启 exposeProxy = true,见上节。


//2、不是 public 方法也失效
private void method() {} // 不生效


//3、try-catch 吞异常,吞了就不回滚
try {
    ...
} catch (Exception e) {
    // 不抛出 → 不回滚
}
//解决办法
//方式1:继续抛异常(最推荐)
catch (Exception e) {
    throw e; // ✅
}
//方式2:手动标记回滚
catch (Exception e) {
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}    
//方式3:抛运行时异常(常见)
catch (Exception e) {
    throw new RuntimeException(e);
}


@Transactional
@Async
//两个一起使用也失效,Spring 的事务上下文是绑定在 ThreadLocal 里的

Seata

Seata失效

1、99% @Transactional失效的情况,@GlobalTransactional 会失效

2、复杂 SQL 会导致 Seata 失效(或不安全)(JOIN UPDATE、 子查询 UPDATE、存储过程、批量复杂操作)

MySQL

第一范式(1NF):每一列都是不可再分的最小数据单元(即列具有原子性)。

第二范式(2NF):表中必须有一个主键,且非主键列必须完全依赖于整个主键,而不能只依赖于主键的一部分(即消除部分依赖)。

第三范式(3NF):非主键列必须直接依赖于主键,不能存在传递依赖。即非主键列之间不能有依赖关系。

索引是提高查询效率的数据结构,类似书的目录

MySQL 索引底层是B+ 树

按逻辑功能分类:

主键索引(PRIMARY KEY):唯一 、 非空

唯一索引(UNIQUE):值不能重复、 可以为 NULL

普通索引(INDEX):最基本的索引,无唯一性约束、仅用于加速查询

复合索引(联合索引):基于多个列创建的索引、遵循最左前缀原则

最左前缀原则指:MySQL 使用联合索引时,必须从索引的最左字段开始,且必须连续使用索引列,否则索引失效。

从左到右,不能跳着用;遇到范围(>、<、between、like),后面全废

-- 可以走索引

WHERE name LIKE 'Tom%'

-- 不能走索引

WHERE name LIKE '%Tom'

'Tom%' 是从左开始匹配

'%Tom' 破坏了最左前缀

B+树里

非叶子:只存索引

叶子:才有数据,存索引加主键,或者全部的值

聚簇索引就是:数据按照索引的顺序存储,索引的叶子节点就是完整的数据行。

其中在 InnoDB 存储引擎中,聚簇索引通常是主键索引;如果没有主键,则会选择第一个唯一且非空的索引作为聚簇索引;如果还没有,则会生成隐藏主键。因此,聚簇索引不一定只能是主键索引。

二级索引(Secondary Index)就是:除了聚簇索引之外的所有索引。叶子存储索引加主键。

回表查询:二级索引查询时需要通过主键回表到聚簇索引获取完整数据。

覆盖索引不是一种索引类型,而是一种查询方式(或查询优化场景)。查询所需要的所有字段,都能从索引中直接获取,不需要回表。

索引失效场景:使用函数、字段参与计算、LIKE 以 % 开头、隐式类型转换、不符合最左前缀、OR 使用不当、

在 MySQL 的 LIKE 查询中,% 是一个通配符(wildcard),表示:匹配任意长度(包括 0 个字符)的任意字符

_ 表示:匹配单个字符

事务四大特性(ACID):A:原子性、C:一致性、I:隔离性、D:持久性

隔离级别

读未提交:脏读

读已提交:不可重复读、其他事务在你两次读取之间修改并提交了数据

可重复读(默认):幻读,类似于读的时候备份,但是新加了数据,范围查询时就可能读的数据变多了,就是幻读。

串行化:无问题。读和写都会被加锁(强制加锁)

MVCC(Multi-Version Concurrency Control,多版本并发控制)通过"保存数据的多个历史版本",让多个事务可以同时读写数据且互不阻塞

MVCC 解决了读写冲突、提高并发性能、避免脏读 / 不可重复读

MVCC 主要依赖 3 个东西: undo log(回滚日志)、 Read View(读视图)、事务 ID(transaction id)

步骤

1️⃣ 创建 Read View:记录:当前活跃事务、哪些事务已经提交

2️⃣ 找到数据版本链:通过 undo log:当前版本 → 旧版本 → 更旧版本

3️⃣ 判断可见性: 看这个版本是否:是当前事务之前提交的?还是之后创建的?

4️⃣ 返回可见版本:找到"对你可见的版本"

MVCC 负责读不加锁,锁机制负责写不冲突

如何优化 SQL?

  • 建索引 ✔
  • 避免 select * ✔
  • 用覆盖索引 ✔
  • 分页优化 ✔
  • 减少子查询 ✔

分页优化

使用"覆盖索引 + 子查询"

游标分页:不用 OFFSET,而是用"上一页的最大值"

记住"上一次查询的值"

避免大 OFFSET

explain结果列说明

id列是一个有顺序的编号,是查询的顺序号,有几个 select 就显示几行。id的顺序是按 select 出现的顺序增长的。id列的值越大执行优先级越高越先执行,id列的值相同则从上往下执行,id列的值为NULL最后执行。

type列的结果表明当前行对应的select的关联类型或访问类型,也就是优化器决定怎么查找数据表中的行,以及查找数据行记录的大概范围。该列的取值优化程度的优劣,从最优到最差依次为:null>system> const > eq_ref > ref > range > index > ALL。一般来说,要保证查询达到range级别,最好达到ref。

key列表明优化器实际采用哪个索引来优化对该表的访问。如果没有使用索引,则该列是 null。

rows列表明优化器大概要读取并检测的行数。跟实际的数据行数大部分情况是不一致的。

MySQL 分库分表是指把原来一个数据库或一张大表的数据,拆分到多个数据库或多张表中,以解决数据量过大带来的性能问题。

分库分表类型

水平分片(常用) 按行拆

垂直分片 按字段拆

MySQL语句

sql 复制代码
SELECT * FROM user;

SELECT * FROM user WHERE age > 18;

SELECT * FROM user ORDER BY age DESC;

#ASC 表示升序(从小到大),DESC 表示降序(从大到小),默认是升序。


#分页
SELECT * FROM user LIMIT 0, 10;
#offset:从第几条开始(偏移量)  size:返回多少条
SELECT * FROM 表名 LIMIT offset, size;

SELECT * FROM user LIMIT 10 OFFSET 20;

#游标分页 优化深分页问题
SELECT * FROM user 
WHERE id > 上一页最后一个id 
LIMIT 10;


SELECT dept_id, COUNT(*) 
FROM user 
GROUP BY dept_id;

SELECT dept_id, COUNT(*) 
FROM user 
GROUP BY dept_id
HAVING COUNT(*) > 10;

#如果连接条件不能唯一匹配(如一对多、多对多),就会产生"多行匹配结果",类似笛卡尔积的效果
SELECT u.name, o.order_id
FROM user u
JOIN orders o ON u.id = o.user_id;


SELECT DISTINCT name FROM user;


SELECT name, COUNT(*) 
FROM user 
GROUP BY name
HAVING COUNT(*) > 1;

#当前时间 2026-04-02 13:00:00
SELECT NOW();

#当前日期
SELECT CURDATE();

#当前时间(时分秒)
SELECT CURTIME();

date 不考虑时区
time
DATETIME
TIMESTAMP 存储有限制:1970~2038 年,考虑时区  年月日 时分秒


#日期转字符串,除了时间戳不行,其他格式的时间都行
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s');

Spring

IoC(控制反转)是指将对象的创建和依赖管理交给 Spring 容器来完成,而不是由程序员手动 new 对象,从而降低耦合,提高系统的可维护性和扩展性。

IoC 是一种设计思想,把对象的控制权交给容器;DI 是它的具体实现方式。

DI(Dependency Injection):由 Spring 容器在运行时,自动把对象(依赖)注入到需要它的类中

构造器注入:通过构造方法注入,不需要加注解,最好加上final防修改。

具有依赖不可变、强制依赖和更适合测试等优点

Setter 注入:把 @Autowired加到set方法上。可能会出现半初始化的问题

优点:可以设置成有就注入,没有也不报错。可以修改(基本不用)

属性注入是通过在成员变量上使用 @Autowired 等注解,由 Spring 在对象实例化后通过反射完成依赖注入的方式。它写法简单,但存在依赖不明确、可能为空以及不利于测试等问题。

半初始化对象:对象已经被创建出来了,但它依赖的对象还没有完全准备好。

主要发生在:属性注入(最容易出现)

可能出现:空指针异常、多线程问题、代码状态不可控

正常情况下,你拿到的 Bean,一定是"已经注入完成"的。Spring 帮你"挡掉了大部分风险

只有在"提前使用了对象"的时候才可能出问题:在构造方法里用依赖、循环依赖 + 提前调用、多线程提前访问

@Autowired 是 Spring 提供的注解,默认按类型注入,如果存在多个 Bean 会结合名称匹配;而 @Resource 是 Java 标准注解,默认按名称注入,找不到再按类型查找。@Autowired 支持构造器注入,而 @Resource 不支持,实际开发中推荐使用 @Autowired 结合构造器注入。

@Autowired 报黄,是因为官方不推荐属性注入,推荐构造器注入。最大的原因是空指针,改成@Resource虽然不报黄,但是治标不治本,问题还是一样存在

Bean 生命周期

1️⃣ 实例化(new 对象)

2️⃣ 依赖注入(DI)

3️⃣ Aware 回调

4️⃣ BeanPostProcessor(初始化前)

5️⃣ 初始化(init)

6️⃣ BeanPostProcessor(初始化后)

7️⃣ 使用 & 销毁

三级缓存是Spring 用来解决"循环依赖"的一套缓存机制

Spring 在创建 Bean 时,用了 3 个 Map:

singletonObjects(一级缓存)

已完成初始化的单例 Bean(成品)

earlySingletonObjects(二级缓存)

提前暴露的 Bean(半成品)

singletonFactories(三级缓存)

用于创建 Bean 的工厂(ObjectFactory)

一级缓存:成品

二级缓存:半成品

三级缓存:生产工具(工厂)

三级缓存✅ 一定会放

二级缓存❌ 只有循环依赖才会用

一级缓存✅ 最终都会进入

每往上走一步:都会从当前缓存移除,再放入下一个缓存

二级缓存"理论上可以解决循环依赖"

但无法保证 AOP 代理正确注入

所以: 必须使用三级缓存

Bean 作用域(Scope) Bean 在容器中的"生命周期范围"和"创建方式"

1️⃣ singleton(默认🔥) 单例模式

2️⃣ prototype 多例模式

AOP(面向切面编程)是一种编程思想,用于将业务逻辑和通用功能(如日志、事务、权限等)分离,通过动态代理在方法执行前后织入增强逻辑。Spring AOP 主要通过 JDK 动态代理或 CGLIB 实现。

在 Spring AOP 中,如果目标对象实现了接口,默认使用 JDK 动态代理;如果没有实现接口,则使用 CGLIB 代理。不过在 Spring Boot 中默认使用 CGLIB,可以通过配置进行切换。JDK 代理基于接口实现,而 CGLIB 通过继承生成子类。

@Component @Service @Repository @Controller @Autowired @Resource @Configuration @Bean @ComponentScan @SpringBootApplication @Transactional

@Aspect 定义切面 @Before / @After / @Around 定义通知类型

SpringMVC

MVC 架构

三层:

Model(数据)

View(视图)

Controller(控制器)

Spring MVC是基于 MVC 设计模式的 Web 框架,用于处理 HTTP 请求

请求(Request)

→ DispatcherServlet(前端控制器) 接收所有请求

→ HandlerMapping(处理器映射器) 根据请求 URL 找到对应的 Controller 方法。返回: 哪个 Controller + 哪个方法

→ HandlerAdapter(处理器适配器)调用对应的 Controller 方法

→ Controller(控制器)接收参数、调用 Service、处理业务逻辑(简单控制)

→ ModelAndView(模型和视图) 把"数据 + 页面"一起返回

→ ViewResolver(视图解析器) 把"视图名"解析成"真正的页面路径"

→ View(视图/页面) 渲染数据 → 生成页面

→ 响应(Response)

@Controller(控制器)

@RestController(REST控制器) @Controller + @ResponseBody 返回 JSON 数据(接口开发常用)

@RequestMapping(请求映射) 映射 URL 路径

@GetMapping / @PostMapping(常用)

@PutMapping / @DeleteMapping

@RequestParam(请求参数) 获取 URL 参数

@PathVariable(路径参数) 获取路径中的参数

@RequestBody(请求体) 接收 JSON 数据

@RequestHeader(请求头)获取请求头信息

@CookieValue(Cookie) 获取 Cookie

@ResponseBody(响应体) 返回 JSON,而不是页面

@ResponseStatus 设置响应状态码

SpringBoot

Spring Boot 核心特性?

  • 自动配置
  • 起步依赖(Starter)
  • 内嵌服务器(Tomcat)

Spring Boot 自动配置 :根据依赖和配置,自动帮你创建和配置 Bean

起步依赖(Starter) : 一组"预先打包好的依赖集合"

启动流程?

  1. 创建 SpringApplication
  2. 启动容器
  3. 自动配置加载
  4. 启动内嵌 Tomcat

Mybatis

读取配置文件,生成SqlSessionFactory对象,调用openSession()获取 sqlSession对象。调用getMapper得到mapper接口,然后调用方法,执行对应的sql语句。

Redis

String、Hash、List、Set、ZSet

持久化

  • RDB(快照)把某一时刻 Redis 的"内存数据"完整保存成一个文件,可能丢数据(最后一次快照之后的数据)
  • AOF(日志)把每一条"写命令"记录下来,

Redis 分布式锁的核心是:利用 SET NX EX 保证"只有一个客户端能获取锁",并通过"唯一标识 + Lua脚本"保证解锁安全。

淘汰策略

LRU(Least Recently Used)淘汰"最久没有使用"的数据

LFU(Least Frequently Used)淘汰"使用频率最低"的数据

Redis 删除过期 key 主要通过两种机制:惰性删除和定期删除。惰性删除是在访问 key 时才检查是否过期,如果过期则删除;定期删除是 Redis 定时随机抽取部分 key 进行检查并删除过期数据。两者结合使用,既保证了访问时数据的正确性,又避免了过期数据长期占用内存。

主从复制,主节点写,从节点同步数据

Redis 的数据同步主要有两种方式:全量同步和增量同步。全量同步是在从节点第一次连接或数据差异较大时触发,主节点通过生成 RDB 文件并发送给从节点实现数据同步;增量同步是在断线后通过复制偏移量和复制积压缓冲区,仅同步断开期间的数据变化,从而提高同步效率。两种方式结合使用,保证了数据的一致性和高效性。

redis加锁

shell 复制代码
#只有当 key 不存在时才设置成功,1 → 加锁成功、0 → 已被占用。不能设置过期时间。
SETNX lock_key value
#NX:不存在才设置(加锁)EX 10:10秒过期
SET lock_key uuid NX EX 10
# 解锁时先判断value,因为锁过期可能会有别的来加锁
if (value == uuid) {
    DEL lock_key
}


#lua脚本的作用,就是写redis命令加简单的逻辑,并且能保证原子性


#redisson使用
RLock lock = redissonClient.getLock("myLock");

try {
    lock.lock(10, TimeUnit.SECONDS); // 自动过期

    // 业务代码
} finally {
    lock.unlock();
}

#Redisson 自动帮你做:
#看门狗机制(自动续期)
#防止锁过期
#防止死锁

Redisson 是一个基于 Redis 的高阶 Java 客户端,封装了分布式锁、限流、队列等高级功能,底层通过 Lua 和 Redis 实现原子操作,极大简化了分布式系统开发。

相比于使用 SET NX EX 手动实现分布式锁,Redisson 提供了自动续期(看门狗机制)、可重入锁、阻塞等待、自动释放等功能,大大提升了锁的安全性和易用性,避免了锁过期、误删等问题,更适合生产环境。

Redisson 看门狗:只有当前线程还在执行才续期。如果:线程挂了。看门狗:自动停止续期

有看门狗,线程卡死也可能导致锁过期,导致别的线程加锁,所以解锁时,要判断是不是自己的锁。

Nacos

三大核心功能:

  1. 服务注册与发现
  2. 配置中心
  3. 动态服务管理

临时实例(ephemeral) 依赖客户端心跳

非临时实例(persistent) 依赖 Nacos 服务端主动探测

CAP 理论是指:一个分布式系统最多只能同时满足以下三点中的两点

C(Consistency)一致性

A(Availability)可用性

P(Partition tolerance)分区容错性

Nacos 在注册中心场景中是偏向 AP(可用性优先)的设计,而不是严格的 CP。

Gateway

网关,用于统一入口,负责请求路由、过滤、鉴权等

RabbitMQ

RabbitMQ 是一个消息队列中间件,用于系统解耦、异步处理和削峰填谷

解耦、异步、削峰、流量控制

Broker 就是 RabbitMQ 服务器本身(消息中间件)

生产者(Producer) → Broker(RabbitMQ) → 消费者(Consumer)

RabbitMQ 核心组成?

  • Producer(生产者)
  • Exchange(交换机)
  • Queue(队列)
  • Consumer(消费者)
  • Binding(绑定):把 Exchange 和 Queue 连接起来,并定义消息路由规则的"关系"

消费者(Consumer)只需要从 Queue(队列)中消费消息,不需要关心 Exchange(交换机)和 Binding(绑定)。

Exchange 有哪些类型?

  • Direct(直连)
  • Fanout(广播)
  • Topic(主题)
  • Headers

Producer(生产者)Exchange(交换机)Queue(队列)Consumer(消费者)

如何保证消息不丢失?

1、生产者确认(Confirm):生产者 → RabbitMQ(Broker),确认消息有没有成功发送到交换机(Exchange)

2、返回机制(Return):Exchange → Queue,当消息无法被路由到任何队列时,把消息退回给生产者

3、消息持久化:队列持久化,还包括交换机持久化和消息本身的持久化

4、消费者手动 ack:告诉 RabbitMQ:这个消息我处理完了,可以删除了

当 Confirm 失败时,通常不会无限重试,而是采用有限次数的重试机制,并结合延迟重试、本地消息表或日志补偿等方式保证消息最终发送成功,同时需要考虑幂等性避免重复消费。

Return 触发 → 记录日志 / 告警 →存入数据库 or 死信队列 → 排查 routing 问题 → 修复后重发

不 ack(或异常),Broker 会认为:你没处理完。 可能:重新投递(requeue)、或进入死信队列

死信队列是消息无法正常消费时进入的队列,死信交换机是绑定死信队列的交换机

触发条件:消息过期、队列满、消费拒绝

延迟队列的本质是让消息在一定时间后再被消费。在 RabbitMQ 中常见的实现方式是通过设置消息 TTL,并结合死信队列实现,当消息过期后进入死信队列再被消费。但这只是实现方式之一,也可以通过延迟插件实现更优雅的延迟机制。

QoS 是 RabbitMQ 提供的一种流量控制机制,通过设置 prefetch 参数来限制消费者一次最多可以处理的未确认消息数量。当消费者处理完一条消息并发送 ACK 后,MQ 才会继续推送下一条消息,从而避免消费者一次性接收过多消息导致内存溢出或服务崩溃。

RocketMQ

Producer(生产者)

Broker(消息存储)

Consumer(消费者)

NameServer(注册中心)类似注册中心、维护 Broker 路由信息、不做数据存储

如何保证消息不丢失?

1️⃣ 生产者发送成功确认

2️⃣ Broker 持久化

3️⃣ 消费者手动 ack

消费者在 RocketMQ 中必须通过"订阅 Topic"来接收消息,同时可以结合 Tag 做更细粒度的过滤。

RocketMQ 有两种消费模式:

1️⃣ 集群消费(负载均衡)

2️⃣ 广播消费

Seata

Seata 是一个分布式事务解决方案,用于保证多个微服务之间数据一致性

Seata 架构组成

  • TC(Transaction Coordinator)事务协调者
  • TM(Transaction Manager)事务发起者
  • RM(Resource Manager)资源管理者

Seata 支持哪些模式

  • AT(默认,最常用)
  • TCC
  • SAGA
  • XA

Feign

Feign 是一个声明式的 HTTP 客户端,用于简化微服务之间的调用

多级缓存

本地缓存(一级)分布式缓存(二级)数据库(兜底)

浏览器缓存

CDN

Nginx

本地缓存(JVM)

Redis

MySQL

浏览器缓存是指浏览器将请求过的资源(HTML、CSS、JS、图片等)存储在本地,下次访问时直接使用,减少网络请求,提高性能

浏览器缓存不是浏览器"自己随便决定的",而是由服务器(后端)通过响应头控制的

通过 HTTP 响应头控制:

Cache-Control

Expires

ETag

Last-Modified

CDN(Content Delivery Network,内容分发网络)是将资源缓存到离用户更近的节点服务器,从而加快访问速度的技术

CDN 的节点服务器是由云厂商提供的,比如阿里云 CDN 已经在全球部署了边缘节点。我们只需要配置源站和缓存策略,用户访问时会自动调度到最近的 CDN 节点,从而实现加速。

CDN必须配置,但"是否缓存、缓存多久"主要由响应头控制

限制。 比如用 Alibaba Cloud CDN:按流量计费、按带宽计费

Nginx 是支持缓存的,通常通过 proxy_cache 实现反向代理缓存,可以将后端接口响应缓存下来,在后续请求中直接返回,从而减少应用服务器和数据库的压力。在高并发场景下,Nginx 缓存通常作为多级缓存体系中的一层,位于 CDN 和应用服务之间。

Nginx 缓存 = 磁盘缓存(主) + 内存索引(辅)

只需要在nginx里配置就能实现。

本地缓存是指数据存储在当前应用进程(JVM)内存中,不经过网络访问

本地缓存是后端(JVM)内部的缓存机制,前端仍然需要请求后端接口,只是后端在处理请求时可以直接从本地缓存中读取数据,而不需要访问数据库或 Redis。

本地缓存常见实现:

  • Map(简单)
  • Caffeine(推荐)
  • Guava Cache

Caffeine 是一个高性能的本地缓存库,运行在 JVM 内存中,提供自动过期、淘汰策略和高并发支持,用于减少对数据库和 Redis 的访问。Caffeine 本质上就是"键值对缓存"

Docker

docker相当于是系统

镜像相当于是软件

拉取镜像,会放在默认目录下

镜像分层,一些软件需要依赖别的同一个软件,相当于一层一层的

容器相当于运行起来的软件。

Dockerfile用于定义镜像的构建过程。

docker build -t myapp:latest . 表示当前目录下找Dockerfile构建

docker build 就是基于基础镜像 + 本地文件,构建一个新的镜像;如果基础镜像本地没有,会自动从远程仓库拉取。

docker run redis 时,如果本地没有 Redis 镜像,Docker 会自动执行 docker pull redis。

只要"本地有"或者"远程仓库有"镜像,就可以 docker run 成功运行容器。

docker pull 是显式拉取镜像;Dockerfile 中通过 FROM 在构建时如果本地没有会自动拉取; docker run 在运行时如果本地没有镜像也会自动拉取。

docker-compose.yml用于定义和运行多容器应用。

systemctl本质是控制:后台运行的服务(service)

systemctl start docker

systemctl stop nginx

systemctl restart mysql

systemctl status xxx # 查看状态

systemctl start docker # 启动docker服务

systemctl stop docker # 停止docker服务

systemctl restart docker # 重启docker服务 restart策略决定容器是否重启

#拉取最新版本的nginx镜像

docker pull nginx

#查看所有镜像

docker images

#把本地nginx镜像的latest版,打包在当前文件夹下nginx.tar

docker save -o nginx.tar nginx:latest

#删除本地镜像

docker rmi nginx:latest

#加载本地文件

docker load -i nginx.tar

#创建并运行nginx容器,容器名,端口,镜像名(默认最新版)

#宿主机端口:容器端口 -d表示后台运行

docker run --name mynginx -p 80:80 -d nginx

docker run --name my -p 3306:3306 -d mysql:5.7.25

docker run --name myweb -p 8090:8090 -d javaweb:1.0

#查看容器 -a所有的 up运行中 Paused暂停 exited结束

docker ps

#查看日志

docker logs mynginx

#查看日志,跟踪日志输出

docker logs -f mynginx

#让一个或多个运行的容器暂停,多个容器空格,容器名或id

docker pause mynginx

#恢复

docker unpause mynginx

docker stop#停止一个运行的容器

docker start#让一个停止的容器再次运行

docker rm#删除一个容器 -f强制

停止容器只是让它"停下来",数据和配置都还在;删除容器是彻底移除,再想用必须重新 docker run。

docker-compose up -d 会根据 docker-compose.yml 文件定义,创建并启动所有配置的服务(services)对应的容器。

docker-compose up -d service_name #只启动某一个

docker-compose down #停止并删除所有容器,不支持指定某一个 service

docker-compose stop redis

docker-compose restart redis

stop后,才可以restart,down后,不可以

资源可以在镜像里,也可以在宿主机挂载。

有这几种变化情况

修改 Dockerfile(镜像变更)

修改 docker-compose.yml(容器配置变更)

修改 jar 包,在镜像里

修改 jar 包,在宿主机挂载。

修改前端静态资源,在镜像里

修改前端静态资源,在宿主机挂载。

全部:docker-compose up -d --build 服务名

就可以解决

有些不需要加build也可以解决

改镜像 → 必须 build

改配置/挂载 → 不用 build

不需要build,这样也行:docker restart 容器

Linux

shell 复制代码
#查看目录文件
ls
#查看目录文件,包含隐藏信息
ls -a
#查看目录文件,包含详细信息
ls -l


#切换目录
# / 根目录  .. 上一级目录  . 当前目录  ~ 家目录
cd
#返回上一次目录
cd -

#显示当前路径
pwd

#创建一个名为 test 的目录(文件夹)
mkdir test

#删除文件
rm file.txt

#删除目录
rm -r dir/

# -f表示强制执行删除操作。删除整个系统
rm -rf /

#创建一个空文件
touch file.txt

#输出:hello
echo hello

#输出到文件,创建文件 + 写入内容,或者覆盖内容
echo "hello" > a.txt
#追加到文件
echo "hello" >> a.txt


#vim
#普通模式(默认)  进入 vim 就是这个模式
#移动光标  删除 / 复制 / 粘贴  查找

#插入模式
#普通模式下,按i|a|o,进入插入模式  光标前插入i 光标后插入a 下一行插入o
#插入模式按Esc进入普通模式

#命令模式(保存退出)
#在普通模式下输入  : 进入命令模式

#  保存 & 退出
:w      保存
:q      退出
:wq     保存并退出
:q!     强制退出

#移动光标
h	左
l	右
j	下
k	上
0     行首
$     行尾
gg    文件开头
G     文件末尾

x      删除字符
dd     删除一行
dw     删除一个单词
d$     删除到行尾

yy     复制一行
p      粘贴

u      撤销
Ctrl + r  重做

/关键字   搜索
n        下一个
N        上一个

>>  缩进
<<  反缩进

#全局替换
:%s/旧/新/g

v   选择字符
V   选择整行

#选择后
#删除
d
#复制
y
#粘贴
p
#替换:删除并进入插入模式
c
#缩进
>
<

#块选择
Ctrl + v

:sp     横向分屏
:vsp    纵向分屏

Ctrl + ]   跳到定义
Ctrl + o   返回

#------------------------------------------------------------------------------------#

#复制文件
cp file1.txt file2.txt
#复制到指定目录
cp file.txt /home/user/
#递归复制整个目录(包括子文件和子目录)
cp -r dir1 dir2


#把 file.txt 移动到 /home/user/ 目录下
mv file.txt /home/user/
#把 old.txt 改名为 new.txt
mv old.txt new.txt
#把整个目录 dir1 移动到 /home/user/
mv dir1 /home/user/
#把目录改名
mv old_dir new_dir

#cat查看文件内容(一次性输出)
#一次性把 file.txt 的所有内容输出到终端
cat file.txt
#创建并写入内容  按 Ctrl + D 结束输入
cat > file.txt
#在文件末尾追加内容
cat >> file.txt
#显示行号
cat -n file.txt
#查找包含 hello 的行
cat file.txt | grep "hello"


#一屏一屏显示内容
more file.txt
空格  下一页
Enter  下一行
q  退出
b  上一页

#从第 10 行开始看
more +10 file.txt

#打开文件进行分页查看
less  file.txt
空格    下一页 
b      上一页  
↑ ↓    上下滚动 
q      退出  

#只显示当前 shell 下的进程(信息很少)
ps
#查看所有进程
ps -ef
#%CPU:CPU占用、%MEM:内存占用、VSZ:虚拟内存、RSS:实际内存
ps aux
#查 nginx 是否运行
ps -ef | grep nginx
#会把 grep java 自己也查出来
ps -ef | grep java
#过滤掉grep java
ps -ef | grep java | grep -v grep
#按用户查看
ps -u root
#按 PID 查
ps -p 1234
#查看父子关系
ps -ef --forest
#按 CPU 排序
ps aux --sort=-%cpu
#找端口对应进程
ps -ef | grep 8080


# grep 是文本搜索工具

#查找 app.log 中包含 error 的行
grep "error" app.log
#忽略大小写
grep -i "error" app.log
#显示行号
grep -n "error" app.log
#显示 不包含 error 的行
grep -v "error" app.log


# 查看文件最后10行(默认)
tail app.log
# 查看最后 20 行
tail -n 20 app.log
# 持续输出
tail -f app.log

#默认显示 前10行
head app.log

#列出当前目录下所有文件和目录
find .
#指定目录查找
find /home
#按文件名查找
find . -name "file.txt"




#权限
r(读)4
w(写)2
x(执行)1

#归属只有两种:
所有者(user)
所属组(group)
other表示其他

#修改所有者
chown tom file.txt
#修改组
chown :dev file.txt
#同时修改
chown tom:dev file.txt

#所有者:rwx(7)组:r-x(5) 其他人:r-x(5)
chmod 755 file.sh




#实时刷新系统状态(默认每3秒刷新一次)
top
P  按 CPU 排序
M  按内存排序
N  按 PID 排序
k  结束进程:输入 PID → 杀进程



#给 PID=1234 的进程发送默认信号(SIGTERM)
kill 1234
#温和结束
kill -15 1234
#强制杀死
kill -9 1234
#重新加载配置(如 nginx)
kill -1 1234


#查看服务器"监听端口"(服务是否启动)
netstat -lntp
-l 只看监听(LISTEN)
-n	数字显示
-t	TCP
-p	显示进程

#查看所有连接 + 对应进程
netstat -anp

#查看 8080 端口被哪个进程占用
lsof -i:8080
#查看某个进程使用了哪些资源
lsof -p 1234
#查看 root 用户打开的所有文件
lsof -u root

#测试 DNS + 网络是否正常
ping www.baidu.com

#直接访问网页(返回 HTML)
curl http://www.baidu.com

#查看磁盘使用情况
df -h
#查看 inode 是否用完(文件数限制)
df -i

#逐个列出子目录占用(单位默认 KB)
du
#查看/var/log目录总大小
du -sh /var/log
#查看当前目录占用
du -sh .
#查看单个文件大小
du -h file.txt


#查看系统内存使用情况
free
#人类可读格式
free -h

# 查看系统的环境变量 PATH
echo $PATH

#直接执行脚本,会打开一个子进程,执行完后,关闭子进程,回到父进程

#在当前 shell 直接执行
source test.sh


PATH是一组目录路径,这些目录里存放着可执行程序
PATH定义位置
系统级配置(所有用户) 常见文件:
/etc/profile
/etc/environment
/etc/bashrc   (有些系统是 /etc/bash.bashrc)
用户级配置(当前用户)常见文件:
~/.bash_profile
~/.bashrc
~/.profile

#export定义环境变量,并导出给子进程使用


#CentOS的yum,自动解决依赖、自动下载
#安装软件
yum install nginx
#卸载软件
yum remove nginx
#更新软件
yum update

#Ubuntu的apt
#安装软件
sudo apt install nginx
#卸载软件、只删除软件本体(配置还在)
sudo apt remove nginx
#完全删除(包含配置)
sudo apt purge nginx
#更新软件列表、更新"软件仓库索引"
sudo apt update
#根据最新列表升级已安装的软件
sudo apt upgrade



#举例,安装并配置java
cd /usr/local
tar -zxvf jdk-17_linux-x64.tar.gz
vim /etc/profile
#添加
export JAVA_HOME=/usr/local/jdk-17
export PATH=$JAVA_HOME/bin:$PATH
#让配置生效
source /etc/profile
#验证
java -version




#启动服务
systemctl start nginx
#停止服务
systemctl stop nginx
#重启服务
systemctl restart nginx
#查看服务状态
systemctl status nginx
#开机自启
systemctl enable nginx
#取消开机自启
systemctl disable nginx
#查看所有服务
systemctl list-units --type=service
#查看开机自启服务
systemctl list-unit-files | grep enabled


#查看防火墙状态
systemctl status firewalld
#启动 / 停止 / 重启
systemctl start firewalld
systemctl stop firewalld
systemctl restart firewalld
#开机自启/取消开机自启
systemctl enable firewalld
systemctl disable firewalld
#查看开放的端口
firewall-cmd --list-ports
#开放端口
firewall-cmd --add-port=8080/tcp --permanent
#然后必须重新加载
firewall-cmd --reload
#删除端口 
firewall-cmd --remove-port=8080/tcp --permanent
firewall-cmd --reload

业务流程

优惠券,每人限领1张,扣减库存和后续生成处理订单应该分两个线程,要怎么设计这个流程

  1. 用户请求

  2. Redis Lua:

    • 判断库存
    • 判断是否领过
    • 原子执行
  3. 成功 → 写入队列

  4. 返回用户(毫秒级)

  5. 消费线程:

    • 创建订单
    • DB唯一索引兜底

1.扎实的 Java 基础,熟悉集合、多线程并发(线程池、锁机制、并发容器等);

2.熟练使用 Spring Framework / Spring Boot / Spring MVC / MyBatis,理解 IOC、

AOP、事务管理机制;

3.熟练使用 MySQL,具备 SQL 优化经验(索引设计、执行计划分析、慢查询优化等);

4.熟练使用 Redis,熟悉缓存穿透、击穿、雪崩解决方案;

5.掌握微服务架构,熟悉 SpringCloudAlibaba 组件(如 Nacos、Gateway、RabbitMQ、

Seata 等);

6.熟悉前端基础(HTML、CSS、JavaScript),具备 Vue + Axios 基本开发能力;

7.熟练使用 Git、Maven,熟悉接口调试(Postman)及单元测试(JUnit)。

Executors 默认队列无限,容易 OOM

OOM = Out Of Memory(内存溢出)

并发容器:在多线程环境下线程安全的集合类

ConcurrentHashMap:替代 HashMap

CopyOnWriteArrayList:读多写少场景,读不加锁,写时复制(Copy-On-Write)

BlockingQueue 是线程安全的阻塞队列,支持生产者-消费者模型。当队列为空时消费者会阻塞等待,当队列满时生产者会阻塞等待。它通常用于解耦生产和消费过程,并通过有界队列控制内存使用,避免系统过载。

修改 MySQL 配置文件,开启慢查询日志

开发环境一般设置 0.5~1 秒,

生产环境一般设置 1~3 秒,根据系统性能动态调整。

分析执行计划 EXPLAIN SELECT ...

索引优化:建索引、联合索引(最左前缀原则)、覆盖索引(避免回表)

单词读音

Async

Transactional

Concurrent

Synchronize

Executors

volatile

Integer

结尾

~/.profile

#export定义环境变量,并导出给子进程使用

#CentOS的yum,自动解决依赖、自动下载

#安装软件

yum install nginx

#卸载软件

yum remove nginx

#更新软件

yum update

#Ubuntu的apt

#安装软件

sudo apt install nginx

#卸载软件、只删除软件本体(配置还在)

sudo apt remove nginx

#完全删除(包含配置)

sudo apt purge nginx

#更新软件列表、更新"软件仓库索引"

sudo apt update

#根据最新列表升级已安装的软件

sudo apt upgrade

#举例,安装并配置java

cd /usr/local

tar -zxvf jdk-17_linux-x64.tar.gz

vim /etc/profile

#添加

export JAVA_HOME=/usr/local/jdk-17

export PATH=JAVAHOME/bin:JAVA_HOME/bin:JAVAHOME/bin:PATH

#让配置生效

source /etc/profile

#验证

java -version

#启动服务

systemctl start nginx

#停止服务

systemctl stop nginx

#重启服务

systemctl restart nginx

#查看服务状态

systemctl status nginx

#开机自启

systemctl enable nginx

#取消开机自启

systemctl disable nginx

#查看所有服务

systemctl list-units --type=service

#查看开机自启服务

systemctl list-unit-files | grep enabled

#查看防火墙状态

systemctl status firewalld

#启动 / 停止 / 重启

systemctl start firewalld

systemctl stop firewalld

systemctl restart firewalld

#开机自启/取消开机自启

systemctl enable firewalld

systemctl disable firewalld

#查看开放的端口

firewall-cmd --list-ports

#开放端口

firewall-cmd --add-port=8080/tcp --permanent

#然后必须重新加载

firewall-cmd --reload

#删除端口

firewall-cmd --remove-port=8080/tcp --permanent

firewall-cmd --reload

复制代码
# 业务流程

优惠券,每人限领1张,扣减库存和后续生成处理订单应该分两个线程,要怎么设计这个流程

1. 用户请求
   
2. Redis Lua:
   - 判断库存
   - 判断是否领过
   - 原子执行
3. 成功 → 写入队列
4. 返回用户(毫秒级)
5. 消费线程:
   - 创建订单
   - DB唯一索引兜底



1.扎实的 Java 基础,熟悉集合、多线程并发(线程池、锁机制、并发容器等);

2.熟练使用 Spring Framework / Spring Boot / Spring MVC / MyBatis,理解 IOC、

AOP、事务管理机制;

3.熟练使用 MySQL,具备 SQL 优化经验(索引设计、执行计划分析、慢查询优化等);

4.熟练使用 Redis,熟悉缓存穿透、击穿、雪崩解决方案;

5.掌握微服务架构,熟悉 SpringCloudAlibaba 组件(如 Nacos、Gateway、RabbitMQ、

Seata 等);

6.熟悉前端基础(HTML、CSS、JavaScript),具备 Vue + Axios 基本开发能力;

7.熟练使用 Git、Maven,熟悉接口调试(Postman)及单元测试(JUnit)。



Executors 默认队列无限,容易 OOM

OOM = Out Of Memory(内存溢出)

并发容器:在多线程环境下线程安全的集合类

ConcurrentHashMap:替代 HashMap

CopyOnWriteArrayList:读多写少场景,读不加锁,写时复制(Copy-On-Write)

BlockingQueue 是线程安全的阻塞队列,支持生产者-消费者模型。当队列为空时消费者会阻塞等待,当队列满时生产者会阻塞等待。它通常用于解耦生产和消费过程,并通过有界队列控制内存使用,避免系统过载。





修改 MySQL 配置文件,开启慢查询日志
开发环境一般设置 0.5~1 秒,
生产环境一般设置 1~3 秒,根据系统性能动态调整。

分析执行计划  EXPLAIN SELECT ...

 索引优化:建索引、联合索引(最左前缀原则)、覆盖索引(避免回表)

# 单词读音

Async

Transactional

Concurrent

Synchronize

Executors

volatile

Integer

# 结尾
相关推荐
ZK_H2 小时前
嵌入式c语言——关键字其6
c语言·开发语言·计算机网络·面试·职场和发展
A.A呐2 小时前
【C++第二十九章】IO流
开发语言·c++
椰猫子2 小时前
Java:异常(exception)
java·开发语言
lifewange2 小时前
pytest-类中测试方法、多文件批量执行
开发语言·python·pytest
cmpxr_2 小时前
【C】原码和补码以及环形坐标取模算法
c语言·开发语言·算法
2401_827499993 小时前
python项目实战09-AI智能伴侣(ai_partner_5-6)
开发语言·python
PD我是你的真爱粉3 小时前
MCP 协议详解:从架构、工作流到 Python 技术栈落地
开发语言·python·架构
win x3 小时前
Redis 使用~如何在Java中连接使用redis
java·数据库·redis
星晨雪海3 小时前
基于 @Resource 的支付 Service 多实现类完整示例
java·开发语言