Java 进阶核心知识点总结 🚀

本文将深入探讨 Java 进阶知识,包括 JVM 内存模型、垃圾回收机制、并发编程、集合源码、设计模式等核心内容。如果你已经掌握了 Java 基础,那么进阶之路从这里开始。


📖 目录

  1. JVM 内存模型
  2. 垃圾回收机制
  3. 类加载机制
  4. 并发编程
  5. 集合源码解析
  6. 设计模式
  7. 常用工具类

JVM 内存模型

为什么需要了解 JVM?

很多初学者可能会问:我写 Java 代码又不需要直接操作内存,JVM 自动管理不就好了吗?

这个想法没错,但如果你想写出高性能资源利用率高的代码,就必须了解 JVM。比如:

  • 什么时候对象会被回收?
  • 为什么代码没问题但内存一直涨?
  • 如何调优 JVM 参数?
  • 如何排查 OOM(OutOfMemoryError)问题?

这些问题都需要对 JVM 有深入了解。

JVM 内存划分

各区域详解

1. 程序计数器(PC Register)

作用:当前线程所执行的字节码的行号指示器。

特点

  • 线程私有,每个线程都有自己的程序计数器
  • 唯一一个不会发生 OutOfMemoryError 的区域
  • 执行 Java 方法时记录字节码指令地址,执行 Native 方法时为空
1
2
3
4
5
6
7
8
9
// 举例:程序计数器的工作方式
public class PCRDemo {
public static void main(String[] args) {
int a = 1; // 字节码指令 0: iconst_1
int b = 2; // 字节码指令 2: iconst_2
int c = a + b; // 字节码指令 3: iadd
// 程序计数器记录当前执行到哪条指令
}
}

2. 虚拟机栈(VM Stack)

作用:存储方法调用时的局部变量、操作数栈、动态链接等信息。

特点

  • 线程私有,生命周期与线程相同
  • 每个方法调用都会创建一个栈帧(Stack Frame)
  • 方法执行完毕,栈帧出栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class StackDemo {
public static void main(String[] args) {
methodA();
}

public static void methodA() {
int a = 10;
methodB(a);
}

public static void methodB(int num) {
int result = num * 2;
System.out.println(result);
}
}

常见错误

  • StackOverflowError:栈溢出,通常是递归调用没有正确终止
  • OutOfMemoryError:内存溢出,栈内存不足
1
2
3
4
5
6
7
8
9
10
// 栈溢出示例:递归没有终止条件
public class StackOverflowDemo {
public static void recursive() {
recursive(); // 无限递归,最终 StackOverflowError
}

public static void main(String[] args) {
recursive();
}
}

3. 本地方法栈(Native Stack)

作用:为 JVM 调用本地方法(Native Method)服务。

与虚拟机栈的区别

  • 虚拟机栈为 Java 方法服务
  • 本地方法栈为 Native 方法服务
  • HotSpot 虚拟机将两者合二为一

4. 堆(Heap)

作用:几乎所有对象实例和数组都在堆上分配内存。

特点

  • 线程共享,是 JVM 管理内存中最大的一块
  • 垃圾收集器(GC)主要管理的就是堆内存
  • 分为年轻代、老年代、永久代(Java 8+ 变为元空间)
1
2
3
4
5
6
7
8
9
10
11
12
// 堆内存演示:创建大量对象
public class HeapDemo {
public static void main(String[] args) {
// 不断创建对象,观察内存变化
List<byte[]> list = new ArrayList<>();
while (true) {
// 每个 byte 数组约 1MB
list.add(new byte[1024 * 1024]);
System.out.println("已分配:" + list.size() + " MB");
}
}
}

5. 方法区(Method Area)

作用:存储类信息(类的字节码、版本号)、常量、静态变量、即时编译器编译后的代码等。

特点

  • 线程共享
  • 在 HotSpot JVM 中,Java 8 之前用永久代实现,Java 8 之后用元空间(Metaspace)实现
  • 元空间使用本地内存,不受 JVM 堆大小限制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MethodAreaDemo {
// 静态变量存储在方法区
private static int staticVar = 100;

// 常量存储在方法区的常量池
public static final String CONSTANT = "Hello";

static {
// 静态代码块也在方法区
System.out.println("类加载时执行");
}

public void method() {
// 普通方法不在方法区,具体在虚拟机栈的栈帧中
}
}

内存溢出示例

1
2
3
4
5
6
7
8
9
10
11
12
// OOM 示例:常量池溢出(Java 7 之前会发生)
public class OOMDemo {
public static void main(String[] args) {
// 使用 String.intern() 将字符串放入常量池
// 如果不断添加字符串到常量池,最终会 OOM
List<String> list = new ArrayList<>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}

垃圾回收机制

什么是垃圾回收?

垃圾回收(Garbage Collection,简称 GC)是 JVM 自动管理内存的机制,自动回收不再使用的对象,防止内存泄漏。

为什么需要 GC?

在 C/C++ 中,内存需要程序员手动分配和释放,容易出现:

  • 内存泄漏:分配后忘记释放,内存越来越少
  • 野指针:释放后继续使用,导致程序崩溃
  • 双重释放:同一块内存释放两次

Java 通过 GC 解决了这些问题,程序员只需要关心对象的创建,不用关心释放。

判断对象是否可回收

1. 引用计数法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器加 1;引用失效时,计数器减 1。任何时刻计数器为 0 的对象就是不可再使用的。

优点:实现简单,判定效率高。

缺点:无法解决循环引用问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 循环引用示例:引用计数法无法处理
public class CircleReference {
public CircleReference ref;

public static void main(String[] args) {
CircleReference a = new CircleReference(); // a 引用计数 = 1
CircleReference b = new CircleReference(); // b 引用计数 = 1

a.ref = b; // b 引用计数 = 2
b.ref = a; // a 引用计数 = 2

// 此时 a 和 b 都不再被其他对象引用(除了互相引用)
// 引用计数都是 2,但实际上已经"死"了

a = null; // a 引用计数 = 1
b = null; // b 引用计数 = 1

// 虽然 a 和 b 已经不可达,但引用计数不为 0
// 引用计数法无法回收这对对象!
}
}

2. 可达性分析(JVM 使用)

通过一系列称为 “GC Roots” 的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,说明这个对象是不可达的,可以被回收。

GC Roots 包括

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 JNI(Native 方法)引用的对象
  • Java 虚拟机内部的引用(Class 对象、异常对象等)
  • 所有被同步锁(synchronized)持有的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class GC RootsDemo {
// GC Roots 1:类的静态属性引用的对象
private static GCRootsDemo instance;

// GC Roots 2:虚拟机栈中引用的对象
private byte[] data; // 占较大内存,避免被优化

public static void main(String[] args) {
// 局部变量表中的引用,作为 GC Roots
GCRootsDemo demo = new GCRootsDemo(); // demo 是 GC Root
demo.data = new byte[10 * 1024 * 1024]; // 10MB

// demo 仍然被 instance 引用,不能回收
instance = demo;

// 解除 demo 引用
demo = null;

// 此时 demo 对象只被 instance 引用
// 仍然是可达的,不能被回收
}
}

垃圾收集算法

1. 标记-清除算法(Mark-Sweep)

步骤

  1. 标记:遍历所有对象,标记出所有需要回收的对象
  2. 清除:统一回收所有被标记的对象

缺点

  • 效率不高
  • 会产生大量内存碎片

2. 复制算法(Copying)

将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块内存用完了,就将还存活的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。

优点:实现简单,运行高效,不会产生内存碎片。

缺点:可用内存缩小为原来的一半,浪费空间。

应用:年轻代使用此算法,因为年轻代对象存活率低。

3. 标记-整理算法(Mark-Compact)

针对老年代的特点,在标记-清除算法的基础上增加了”整理”步骤。标记过程与标记-清除算法一样,但后续不是直接清理可回收对象,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。

优点:不会产生内存碎片。

缺点:整理过程需要移动对象,效率较低。

4. 分代收集算法(Generational Collection)

当前商业 JVM 普遍采用这种算法。根据对象存活周期的不同将内存划分为几块,一般把 Java 堆分为年轻代和老年代,然后根据各年代的特点采用最适当的收集算法。

垃圾收集器

Serial 收集器

最基本、历史最悠久的收集器。是一个单线程收集器,在进行垃圾收集时,必须暂停所有其他工作线程(Stop The World)。

特点:简单高效,Client 模式下默认的年轻代收集器。

1
2
# 指定使用 Serial 收集器
-XX:+UseSerialGC

ParNew 收集器

Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为与 Serial 完全一样。

特点:Server 模式下首选的年轻代收集器,可以与 CMS 配合使用。

1
2
# 指定使用 ParNew 收集器
-XX:+UseParNewGC

Parallel Scavenge 收集器

关注点是吞吐量(Throughput),即吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。

特点:自适应调节策略,GC 自适应调节参数。

1
2
3
4
5
6
# 指定使用 Parallel Scavenge 收集器
-XX:+UseParallelGC
# 设置最大 GC 暂停时间
-XX:MaxGCPauseMillis=100
# 设置吞吐量大小
-XX:GCTimeRatio=99

CMS 收集器

Concurrent Mark Sweep,以获取最短回收停顿时间为目标的收集器。

收集过程

  1. 初始标记(Initial Mark):标记 GC Roots 能直接关联到的对象
  2. 并发标记(Concurrent Mark):进行 GC Roots Tracing
  3. 重新标记(Remark):修正并发标记期间因用户程序继续运作而导致标记产生变动
  4. 并发清除(Concurrent Sweep):清理垃圾

缺点

  • 对 CPU 资源敏感,会占用 CPU 资源
  • 无法处理浮动垃圾(并发清理阶段产生的垃圾)
  • 产生内存碎片
1
2
3
4
# 指定使用 CMS 收集器
-XX:+UseConcMarkSweepGC
# 触发 CMS 的阈值
-XX:CMSInitiatingOccupancyFraction=68

G1 收集器

Garbage First,将整个堆划分为多个大小相等的 Region,跟踪各个 Region 里面的垃圾堆积价值(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。

特点

  • 并行与并发
  • 分代收集
  • 空间整合(不产生内存碎片)
  • 可预测的停顿
1
2
3
4
5
6
# 指定使用 G1 收集器
-XX:+UseG1GC
# 设置目标停顿时间
-XX:MaxGCPauseMillis=200
# 设置 Region 大小
-XX:G1HeapRegionSize=mb

类加载机制

类的生命周期

类加载的过程

1. 加载(Loading)

“加载”是”类加载”过程的一个阶段,两者不可混淆。

此阶段完成的事情

  • 通过类的全限定名获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 类的加载:Class 对象是访问类元数据的入口
public class LoadingDemo {
public static void main(String[] args) {
// 获取 Class 对象的方式
// 方式一:Class.forName()
Class<?> clazz1 = Class.forName("java.lang.String");

// 方式二:.class 属性
Class<?> clazz2 = String.class;

// 方式三:对象的 getClass() 方法
String str = "hello";
Class<?> clazz3 = str.getClass();

// 三个 Class 对象是同一个(类的 Class 对象在 JVM 中唯一)
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz2 == clazz3); // true
}
}

2. 验证(Verification)

确保 Class 文件的字节流中包含的信息符合当前 JVM 要求,不会危害 JVM 安全。

验证阶段包括

  • 文件格式验证:验证字节流是否符合 Class 文件格式规范
  • 元数据验证:对字节码描述的信息进行语义分析
  • 字节码验证:通过数据流和控制流分析,确保程序语义的合法性
  • 符号引用验证:确保解析动作能正常执行

3. 准备(Preparation)

正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。

1
2
3
4
5
6
7
8
9
10
public class PreparationDemo {
// 类变量:准备阶段赋默认值
private static int staticVar = 100;

// 常量:准备阶段赋最终值
private static final int CONSTANT = 200;

// 编译时常量在准备阶段就被替换为具体值
// 因为它是 static final,编译器就知道值是多少
}

注意

  • 这时候进行内存分配的仅包括类变量(static 修饰的变量),不包括实例变量
  • 实例变量会在对象实例化时随对象一起分配在堆中
  • 初始值通常是零值,而非代码中赋予的值
类型 零值
int 0
long 0L
short (short) 0
byte (byte) 0
char ‘\u0000’
boolean false
reference null
float 0.0f
double 0.0d

4. 解析(Resolution)

将常量池内的符号引用替换为直接引用的过程

  • 符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量
  • 直接引用:直接指向目标的指针、相对偏移量或能间接定位到目标的句柄
1
2
3
4
5
6
7
8
9
10
11
12
// 解析示例
public class ResolutionDemo {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("java.lang.String");

// 获取方法或字段时,JVM 会进行解析
// 符号引用 -> 直接引用
java.lang.reflect.Field field =
clazz.getDeclaredField("value");
// field 是一个直接引用,指向 actual character array
}
}

5. 初始化(Initialization)

类加载过程的最后一步,真正执行类中定义的 Java 代码。

触发初始化的场景(主动引用)

  1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时
  2. 使用 java.lang.reflect 包的方法对类进行反射调用时
  3. 当初始化一个类时,发现其父类还没初始化,需要先触发父类初始化
  4. JVM 启动时,会先初始化包含 main() 方法的那个类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class InitializationDemo {
static {
System.out.println("InitializationDemo 被初始化!");
}

public static void main(String[] args) {
// 触发 InitializationDemo 初始化
System.out.println("main 方法执行");

// 触发 A 初始化
// A a = new A(); // 如果有 A 类,会触发其初始化
}
}

class A {
static {
System.out.println("A 被初始化!");
}
}

类加载器

三种默认类加载器

  1. Bootstrap ClassLoader(启动类加载器)

    • 最顶层的类加载器
    • 由 C++ 实现,负责加载 JAVA_HOME/lib 目录中的类库
    • 无法被 Java 程序直接引用
  2. Extension ClassLoader(扩展类加载器)

    • 由 Java 实现(ExtClassLoader
    • 负责加载 JAVA_HOME/lib/ext 目录中的类库
    • 开发者可以把自己写的类打包成 jar 放到此目录
  3. Application ClassLoader(应用类加载器)

    • 由 Java 实现(AppClassLoader
    • 负责加载 classpath 上指定的类库
    • 一般情况下这是程序中默认的类加载器

双亲委派模型

工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

为什么需要双亲委派模型?

  • 安全性:防止核心类被篡改。比如用户自己写一个 java.lang.String 类,由于双亲委派模型,这个类永远不会被加载,因为会优先使用父加载器加载的 java.lang.String
  • 避免重复加载:父加载器已加载的类,子加载器不需要再次加载。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 自定义类加载器示例
public class MyClassLoader extends ClassLoader {
private String classPath;

public MyClassLoader(String classPath) {
this.classPath = classPath;
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException(name);
}
return defineClass(name, classData, 0, classData.length);
}

private byte[] loadClassData(String className) {
// 从文件系统读取 class 文件
String fileName = classPath + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
try (InputStream is = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
int data;
while ((data = is.read()) != -1) {
baos.write(data);
}
return baos.toByteArray();
} catch (IOException e) {
return null;
}
}
}

并发编程

为什么要并发?

单线程的问题

  • 多核 CPU 环境下,只用一个核太浪费
  • I/O 等待时 CPU 空闲,效率低

多线程的优势

  • 提高 CPU 利用率
  • 提高程序吞吐量
  • 便于异步处理
  • 提升用户体验

并发编程三大概念

1. 原子性(Atomicity)

一个操作或多个操作要么全部执行,要么全部不执行。

示例:i++ 不是原子操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class AtomicityDemo implements Runnable {
private int count = 0;

@Override
public void run() {
for (int i = 0; i < 10000; i++) {
count++; // 这不是原子操作!
}
}

public static void main(String[] args) throws InterruptedException {
AtomicityDemo demo = new AtomicityDemo();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
t2.start();
t1.join();
t2.join();

// 期望:20000,实际可能小于 20000
System.out.println("最终结果:" + demo.count);
}
}

count++ 实际上分为三步:

  1. 读取 count 的值
  2. 将 count 加 1
  3. 写入新的 count 值

多线程下可能导致数据不一致。

解决方案:使用 AtomicInteger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo implements Runnable {
private AtomicInteger count = new AtomicInteger(0);

@Override
public void run() {
for (int i = 0; i < 10000; i++) {
count.incrementAndGet(); // 原子操作
}
}

public int getCount() {
return count.get();
}
}

2. 可见性(Visibility)

一个线程对共享变量的修改,其他线程能够立即看到。

示例:可见性问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class VisibilityDemo extends Thread {
// 加上 volatile 关键字
private volatile boolean running = true;

@Override
public void run() {
while (running) {
// 无限循环
}
System.out.println("线程结束了");
}

public void stopRunning() {
running = false;
}

public static void main(String[] args) throws InterruptedException {
VisibilityDemo demo = new VisibilityDemo();
demo.start();

Thread.sleep(1000);
demo.stopRunning(); // 尝试停止线程

// 如果没有 volatile,主线程修改 running 后
// 子线程可能看不到(缓存问题),导致无法停止
}
}

3. 有序性(Ordering)

程序执行的顺序按照代码的先后顺序执行。

问题:编译器和处理器可能会对指令进行重排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 有序性问题示例
public class OrderingDemo {
private int a = 0;
private boolean flag = false;

public void writer() {
a = 1; // 1. 给 a 赋值
flag = true; // 2. 设置 flag
}

public void reader() {
if (flag) { // 3. 读取 flag
int i = a; // 4. 读取 a
System.out.println(i);
}
}
}

指令重排后可能的执行顺序:

  1. flag = true
  2. a = 1
  3. 读取 a(此时 a = 1)
  4. 读取 flag(此时 flag = true)

导致 i 的值可能不是我们期望的 1。

volatile 关键字

volatile 是 Java 中最轻量的同步机制,它有两个作用:

  1. 保证可见性:被 volatile 修饰的变量,修改后会立即写回主内存,并让其他线程的缓存失效
  2. 禁止指令重排序:volatile 变量操作前后的代码不能被重排序
1
2
3
4
5
6
7
8
public class VolatileDemo {
// volatile 保证:每次读取都从主内存读,写完立即刷新到主内存
private volatile int count = 0;

public void increment() {
count++; // 不是原子操作,但 volatile 保证可见性
}
}

volatile 的适用场景

  • 状态标志位(如 running)
  • 双重检查锁定(Double Checked Locking)
  • 观察量(observer)

volatile 的局限性

  • 不能保证原子性(如 count++)
  • 不能替代 synchronized

synchronized 关键字

synchronized 是 Java 中最基础的同步机制,可以保证:

  • 原子性
  • 可见性
  • 有序性

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class SynchronizedDemo {

// 1. 修饰实例方法 - 锁对象本身
public synchronized void method1() {
// 同一时刻只有一个线程能执行这个方法
}

// 2. 修饰静态方法 - 锁类对象
public static synchronized void method2() {
// 同一时刻只有一个线程能执行这个方法
}

// 3. 修饰代码块 - 锁指定对象
public void method3() {
synchronized (this) { // 锁当前对象
// 同步代码
}
}

public void method4() {
synchronized (SynchronizedDemo.class) { // 锁类对象
// 同步代码
}
}
}

Synchronized 原理

线程池

线程池是并发编程中最重要的组件之一,它避免了频繁创建和销毁线程的开销。

线程池参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ThreadPoolDemo {
public static void main(String[] args) {
// 七大参数
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize:核心线程数
5, // maximumPoolSize:最大线程数
60L, // keepAliveTime:空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(3), // workQueue:任务队列
Executors.defaultThreadFactory(), // threadFactory:线程工厂
new ThreadPoolExecutor.AbortPolicy() // handler:拒绝策略
);

// 提交 8 个任务测试
for (int i = 1; i <= 8; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("任务 " + taskId + " 由线程 "
+ Thread.currentThread().getName() + " 执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}

executor.shutdown();
}
}

拒绝策略

策略 行为
AbortPolicy 抛出 RejectedExecutionException(默认)
CallerRunsPolicy 由调用者线程执行
DiscardPolicy 静默丢弃任务
DiscardOldestPolicy 丢弃队列中最老的任务

Executors 工具类创建线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ExecutorsDemo {
public static void main(String[] args) {
// 1. 固定线程数线程池(适合CPU密集型)
ExecutorService fixedPool =
Executors.newFixedThreadPool(5);

// 2. 单线程线程池(保证顺序执行)
ExecutorService singlePool =
Executors.newSingleThreadExecutor();

// 3. 缓存线程池(适合IO密集型,任务多但执行快)
ExecutorService cachedPool =
Executors.newCachedThreadPool();

// 4. 定时线程池
ScheduledExecutorService scheduledPool =
Executors.newScheduledThreadPool(3);

// 5. 工作窃取线程池
ExecutorService workStealingPool =
Executors.newWorkStealingPool();

// 注意:实际生产中推荐使用 ThreadPoolExecutor
// Executors 创建的线程池可能有 OOM 风险
}
}

JUC 并发工具类

CountDownLatch

门闩,等待一组线程完成后再执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);

for (int i = 1; i <= 3; i++) {
final int playerId = i;
new Thread(() -> {
System.out.println("选手 " + playerId + " 准备完成");
latch.countDown(); // 完成一项准备
}).start();
}

// 等待所有选手准备完成
latch.await();
System.out.println("所有选手准备完毕,开始比赛!");
}
}

CyclicBarrier

循环栅栏,让一组线程互相等待,达到某个点后全部放行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有玩家都准备好了,游戏开始!");
});

for (int i = 1; i <= 3; i++) {
final int playerId = i;
new Thread(() -> {
System.out.println("玩家 " + playerId + " 加载中...");
try {
Thread.sleep((long) (Math.random() * 1000));
System.out.println("玩家 " + playerId + " 准备完成,等待其他人...");
barrier.await(); // 等待其他玩家
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}

Semaphore

信号量,控制同时访问某个资源的线程数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SemaphoreDemo {
// 模拟停车位,只有 3 个
private static final int SPOTS = 3;
private static final Semaphore semaphore = new Semaphore(SPOTS);

public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
final int carId = i;
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可证
System.out.println("车辆 " + carId + " 进入停车场");
Thread.sleep((long) (Math.random() * 3000));
System.out.println("车辆 " + carId + " 离开停车场");
semaphore.release(); // 释放许可证
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}

ConcurrentHashMap

线程安全的 HashMap,高并发场景首选。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ConcurrentHashMapDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map =
new ConcurrentHashMap<>();

// 常用操作
map.put("A", 1);
map.putIfAbsent("B", 2); // 不存在才插入
map.get("A");
map.remove("A");
map.size();

// 原子操作
map.putIfAbsent("counter", 0);
// 原子递增
map.replace("counter", map.get("counter"),
map.get("counter") + 1);

// Java 8+ 支持 compute 方法
map.compute("counter", (k, v) -> v == null ? 1 : v + 1);

// computeIfPresent:key 存在才更新
map.computeIfPresent("counter", (k, v) -> v + 1);

// computeIfAbsent:key 不存在才添加
map.computeIfAbsent("newCounter", k -> 10);
}
}

集合源码解析

HashMap 底层实现

HashMap 是 Java 中最常用的 Map 实现,了解其源码对于深入理解数据结构至关重要。

JDK 1.7 vs JDK 1.8

HashMap 基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// HashMap 核心属性
public class HashMap<K, V> extends AbstractMap<K, V>
implements Map<K, V>, Cloneable, Serializable {

// 默认初始容量(16)
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

// 最大容量(2^30)
static final int MAXIMUM_CAPACITY = 1 << 30;

// 负载因子(0.75,元素达到容量的75%时扩容)
static final float DEFAULT_LOAD_FACTOR = 0.75f;

// 树化阈值(链表转红黑树)
static final int TREEIFY_THRESHOLD = 8;

// 解除树化阈值(红黑树转链表)
static final int UNTREEIFY_THRESHOLD = 6;

// 最小树化容量(整个 HashMap 桶数 >= 64 才允许树化)
static final int MIN_TREEIFY_CAPACITY = 64;

// 存储数据的数组
transient Node<K, V>[] table;

// 元素个数
transient int size;
}

hash() 方法详解

1
2
3
4
5
6
// HashMap 的 hash 算法
static final int hash(Object key) {
int h;
// 扰动函数:让高位参与运算,减少碰撞
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

为什么用 (n-1) & hash 而不是 hash % n?

  • n 必须是 2 的幂次方(如 16)
  • n - 1 的二进制是低位全为 1
  • & 操作比 % 操作快得多
1
2
3
4
5
6
// 计算桶位置
int index = hash(key) & (capacity - 1);

// 例如:capacity = 16 = 2^4,capacity - 1 = 15 = 0b1111
// hash(key) = 31 = 0b11111
// index = 0b11111 & 0b1111 = 0b1111 = 15

put() 方法流程

扩容机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 扩容方法
Node<K, V>[] resize() {
// 新容量 = 旧容量 * 2
newCap = oldCap << 1;

// 新阈值 = 旧阈值 * 2
newThr = oldThr << 1;

// 创建新数组
Node<K, V>[] newTable = new Node[newCap];

// 迁移元素(重点)
for (int j = 0; j < oldCap; j++) {
Node<K, V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null) {
// 只有一个节点,直接计算新位置
newTab[e.hash & (newCap - 1)] = e;
} else if (e instanceof TreeNode) {
// 红黑树拆分
((TreeNode<K, V>) e).split(this, newTable, j, oldCap);
} else {
// 链表拆分(JDK 1.8 尾插法)
Node<K, V> loHead = null, loTail = null;
Node<K, V> hiHead = null, hiTail = null;
Node<K, V> next;
do {
next = e.next;
// 原位置节点
if ((e.hash & oldCap) == 0) {
if (loTail == null) loHead = e;
else loTail.next = e;
loTail = e;
} else {
// 扩容后的新位置节点
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;
}
}
}
}
return newTable;
}

扩容后元素位置规律

  • (e.hash & oldCap) == 0 → 留在原位置
  • (e.hash & oldCap) != 0 → 移动到原位置 + oldCap

ConcurrentHashMap 源码解析

JDK 1.7 分段锁

每个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表节点。并发度由 Segment 数组大小决定,默认 16。

JDK 1.8 CAS + synchronized

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ConcurrentHashMapDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map =
new ConcurrentHashMap<>();

// CAS + synchronized 保证了并发安全
map.put("A", 1);
map.putIfAbsent("B", 2); // CAS 操作

// 原子操作
map.putIfAbsent("counter", 0);
map.put("counter", map.get("counter") + 1);

// 推荐:使用 compute 原子更新
map.compute("counter", (k, v) -> v == null ? 1 : v + 1);
}
}

设计模式

设计模式分类

单例模式

确保一个类只有一个实例,并提供一个全局访问点。

饿汉式(线程安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HungrySingleton {
// 类加载时就创建实例,线程安全
private static final HungrySingleton INSTANCE = new HungrySingleton();

// 私有构造函数
private HungrySingleton() {
// 防止反射创建多个实例
if (INSTANCE != null) {
throw new RuntimeException("单例对象已存在!");
}
}

public static HungrySingleton getInstance() {
return INSTANCE;
}
}

懒汉式(线程不安全)

1
2
3
4
5
6
7
8
9
10
11
12
public class LazySingleton {
private static LazySingleton instance;

private LazySingleton() {}

public static LazySingleton getInstance() {
if (instance == null) { // 线程不安全
instance = new LazySingleton();
}
return instance;
}
}

双重检查锁定(线程安全,推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DoubleCheckSingleton {
// volatile 防止指令重排序
private static volatile DoubleCheckSingleton instance;

private DoubleCheckSingleton() {}

public static DoubleCheckSingleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (DoubleCheckSingleton.class) {
if (instance == null) { // 第二次检查
instance = new DoubleCheckSingleton();
// 可能的指令重排序:
// 1. 分配内存
// 2. 调用构造函数
// 3. instance 指向内存
// volatile 防止重排序,保证 2 在 3 之前
}
}
}
return instance;
}
}

静态内部类(线程安全,推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {}

// 静态内部类,只有在被调用时才会加载,且加载过程是线程安全的
private static class SingletonHolder {
private static final StaticInnerClassSingleton INSTANCE =
new StaticInnerClassSingleton();
}

public static StaticInnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

工厂模式

简单工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 产品接口
interface Phone {
void call();
}

// 具体产品
class IPhone implements Phone {
@Override
public void call() {
System.out.println("用 iPhone 打电话");
}
}

class AndroidPhone implements Phone {
@Override
public void call() {
System.out.println("用安卓手机打电话");
}
}

// 工厂
class SimplePhoneFactory {
public static Phone createPhone(String type) {
switch (type) {
case "iPhone":
return new IPhone();
case "Android":
return new AndroidPhone();
default:
throw new IllegalArgumentException("未知类型");
}
}
}

工厂方法模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 抽象工厂
interface PhoneFactory {
Phone createPhone();
}

// 具体工厂
class IPhoneFactory implements PhoneFactory {
@Override
public Phone createPhone() {
return new IPhone();
}
}

class AndroidFactory implements PhoneFactory {
@Override
public Phone createPhone() {
return new AndroidPhone();
}
}

// 使用
public class FactoryMethodDemo {
public static void main(String[] args) {
PhoneFactory factory = new IPhoneFactory();
Phone phone = factory.createPhone();
phone.call();
}
}

抽象工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 产品族接口
interface Laptop {
void display();
}

interface Phone {
void call();
}

// 苹果产品族
class MacBook implements Laptop {
@Override
public void display() {
System.out.println("MacBook 显示");
}
}

class iPhone implements Phone {
@Override
public void call() {
System.out.println("iPhone 打电话");
}
}

// 抽象工厂
interface DeviceFactory {
Laptop createLaptop();
Phone createPhone();
}

// 苹果工厂
class AppleFactory implements DeviceFactory {
@Override
public Laptop createLaptop() {
return new MacBook();
}

@Override
public Phone createPhone() {
return new iPhone();
}
}

策略模式

定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 策略接口
interface SortStrategy {
void sort(int[] array);
}

// 具体策略:冒泡排序
class BubbleSort implements SortStrategy {
@Override
public void sort(int[] array) {
System.out.println("使用冒泡排序");
// 冒泡排序实现...
}
}

// 具体策略:快速排序
class QuickSort implements SortStrategy {
@Override
public void sort(int[] array) {
System.out.println("使用快速排序");
// 快速排序实现...
}
}

// 上下文:使用策略的对象
class Sorter {
private SortStrategy strategy;

public void setStrategy(SortStrategy strategy) {
this.strategy = strategy;
}

public void sort(int[] array) {
strategy.sort(array);
}
}

// 使用
public class StrategyDemo {
public static void main(String[] args) {
int[] array = {3, 1, 4, 1, 5, 9, 2, 6};

Sorter sorter = new Sorter();

sorter.setStrategy(new BubbleSort());
sorter.sort(array);

sorter.setStrategy(new QuickSort());
sorter.sort(array);
}
}

代理模式

为其他对象提供一种代理以控制对这个对象的访问。

静态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 接口
interface Image {
void display();
}

// 真实对象
class RealImage implements Image {
private String filename;

public RealImage(String filename) {
this.filename = filename;
loadFromDisk(); // 模拟加载
}

@Override
public void display() {
System.out.println("显示图片:" + filename);
}

private void loadFromDisk() {
System.out.println("从磁盘加载:" + filename);
}
}

// 代理对象
class ImageProxy implements Image {
private RealImage realImage;
private String filename;

public ImageProxy(String filename) {
this.filename = filename;
}

@Override
public void display() {
// 懒加载:真正需要时才加载真实对象
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}

// 使用
public class ProxyDemo {
public static void main(String[] args) {
Image image = new ImageProxy("photo.jpg");

// 此时不会加载真实图片
System.out.println("图片代理已创建");

// 真正显示时才加载
image.display();
image.display(); // 第二次不需要重新加载
}
}

JDK 动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKDynamicProxy {
public static void main(String[] args) {
RealImage realImage = new RealImage("photo.jpg");

Image proxy = (Image) Proxy.newProxyInstance(
RealImage.class.getClassLoader(),
RealImage.class.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
System.out.println("前置操作");
Object result = method.invoke(realImage, args);
System.out.println("后置操作");
return result;
}
}
);

proxy.display();
}
}

常用工具类

StringUtils 常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import org.apache.commons.lang3.StringUtils;

public class StringUtilsDemo {
public static void main(String[] args) {
String str = " hello world ";

// 判断空
StringUtils.isEmpty(str); // false(有空格)
StringUtils.isBlank(str); // false(有空格)
StringUtils.isBlank(""); // true
StringUtils.isBlank(null); // true

// 去除空白
StringUtils.trim(str); // "hello world"
StringUtils.trimToNull(str); // "hello world",空白返回null
StringUtils.trimToEmpty(str); // "hello world",空白返回""

// 判断相等
StringUtils.equals("abc", "abc"); // true
StringUtils.equalsIgnoreCase("abc", "ABC"); // true

// 字符串包含
StringUtils.contains("abc", "b"); // true
StringUtils.containsIgnoreCase("ABC", "b"); // true

// 截取
StringUtils.substring("hello world", 6); // "world"
StringUtils.substring("hello world", 0, 5); // "hello"

// 分割
StringUtils.split("a,b,c", ","); // ["a", "b", "c"]

// 连接
StringUtils.join(["a", "b", "c"], "-"); // "a-b-c"

// 重复
StringUtils.repeat("ab", 3); // "ababab"

// 反转
StringUtils.reverse("hello"); // "olleh"

// 大小写
StringUtils.upperCase("hello"); // "HELLO"
StringUtils.lowerCase("HELLO"); // "hello"
StringUtils.capitalize("hello"); // "Hello"
}
}

CollectionUtils 常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import org.apache.commons.collections4.CollectionUtils;

public class CollectionUtilsDemo {
public static void main(String[] args) {
List<String> list1 = Arrays.asList("A", "B", "C");
List<String> list2 = Arrays.asList("B", "C", "D");

// 判断空
CollectionUtils.isEmpty(list1); // false
CollectionUtils.isNotEmpty(list1); // true

// 交集
List<String> intersection =
new ArrayList<>(CollectionUtils.intersection(list1, list2));
// [B, C]

// 并集
CollectionUtils.union(list1, list2);
// [A, B, C, D]

// 差集(list1 - list2)
CollectionUtils.subtract(list1, list2);
// [A]

// 判断是否有交集
CollectionUtils.containsAny(list1, list2); // true

// 过滤
List<String> filtered = list1.stream()
.filter(s -> s.equals("A"))
.collect(Collectors.toList());

// 判空并赋值默认值
List<String> safeList =
CollectionUtils.isEmpty(list1) ? Collections.emptyList() : list1;
}
}

Objects 常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.Objects;

public class ObjectsDemo {
public static void main(String[] args) {
// 判断相等
Objects.equals("abc", "abc"); // true
Objects.equals(null, "abc"); // false
Objects.equals(null, null); // true

// 判断是否为 null
Objects.isNull(null); // true
Objects.nonNull(null); // false

// requireNonNull:参数校验
public void method(String param) {
// param 为 null 时抛出 NullPointerException
Objects.requireNonNull(param, "参数不能为空");
}

// toString 安全版本
Objects.toString(null); // "null"
Objects.toString(null, "默认值"); // "默认值"

// hashCode(对 null 返回 0)
Objects.hashCode(null); // 0
}
}

Optional 最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.Optional;

public class OptionalDemo {
public static void main(String[] args) {
// 创建 Optional
Optional<String> empty = Optional.empty();
Optional<String> of = Optional.of("hello");
Optional<String> nullable = Optional.ofNullable(null);

// 判断并获取
of.isPresent(); // true
of.isEmpty(); // false

// 获取值(可能抛异常)
of.get(); // "hello"
// nullable.get(); // NoSuchElementException

// 获取值或默认值
nullable.orElse("default"); // "default"
nullable.orElseGet(() -> "computed"); // "computed"
// nullable.orElseThrow(); // NoSuchElementException

// ifPresent
of.ifPresent(System.out::println);

// map 转换
of.map(String::toUpperCase); // Optional["HELLO"]

// flatMap(返回 Optional)
of.flatMap(s -> Optional.of(s.trim()));

// filter 过滤
of.filter(s -> s.length() > 3); // Optional["hello"]

// 链式调用
String result = Optional.ofNullable(user)
.map(User::getName)
.map(String::toUpperCase)
.orElse("匿名");
}
}

📌 总结

进阶学习建议

  1. 多读源码:JDK 源码、Spring 源码、MyBatis 源码
  2. 手写实现:自己实现一遍 HashMap、线程池等
  3. 性能调优:学习 JVM 调优、SQL 调优
  4. 分布式:学习 Redis、RocketMQ、Kafka
  5. 微服务:Spring Cloud、Dubbo、Service Mesh

向下扎根,向上生长 🌱


📅 本文首次发布于 2026 年 5 月 20 日