Java基础知识总结

Java基础知识总结

本文整合了 Java 虚拟机、集合框架、文件IO、反射、注解、代理、线程、锁、并发编程等核心基础知识。


目录

  1. Java虚拟机(JVM)
  2. Java集合框架
  3. Java文件IO
  4. Java反射
  5. Java注解
  6. Java代理
  7. Java线程
  8. Java锁
  9. Java并发编程

一、Java虚拟机(JVM)

1.1 什么是Java虚拟机

Java 跨平台的原理:Java 语言不直接将代码编译成与系统相关的机器码,而是编译成一种特定的语言规范——字节码(Bytecode)。Java 虚拟机会解析字节码文件并将其翻译为各操作系统能理解的机器码。

JVM 本质上是一个字节码翻译器。

一次编译到处运行

1.2 编译过程

编译器分为三类:

编译器类型 作用 特点
前端编译器(javac) 源代码 → 字节码 .java 编译为 .class
JIT 编译器(即时编译器) 字节码 → 机器码 运行时编译热点代码,HotSpot 内置 C1(编译快、优化保守)和 C2(编译慢、优化好)两种模式
AOT 编译器(静态提前编译器) 源代码 → 机器码 减少”第一次运行慢”的体验

前端编译过程:词法分析/语法分析 → 填充符号表 → 分析注解 → 生成字节码。

1.3 字节码文件结构

字节码文件是一组以 8 位为最小基础的十六进制数据流,由无符号数组成。

核心组成部分:

  • 魔数:前 4 字节固定为 0xCAFEBABE,用于识别 Class 文件
  • 文件版本:第 5-8 字节,包含次版本号和主版本号(如 JDK 1.8 = 0x00000034
  • 常量池:存放字面量和符号引用
  • 访问标志:标识类或接口的访问信息(public、abstract 等)
  • 类索引/父类索引/接口索引:确定类的继承关系
  • 字段表:描述类中声明的变量
  • 方法表:描述类中的方法
  • 属性表:描述类的属性信息

可通过 javap -verbose 类名.class 反编译查看字节码。

1.4 JVM 内存结构

JVM 内存分为公有(线程共享)和私有(线程独有)两部分:

公有部分

Java 堆:几乎所有对象实例都在此分配内存。按对象存活时间分为:

  • 新生代(Young Generation):
    • Eden 区:新对象的出生地
    • SurvivorFrom / SurvivorTo:Minor GC 时,幸存对象在两者之间复制(复制算法
    • 默认比例 Eden : From : To = 8 : 1 : 1
  • 老年代(Old Generation):存活时间长的对象,采用标记清除法标记压缩法
  • 方法区/元空间:存放 Class 和 Meta 信息。JDK 8 后用元空间(Metaspace)取代永久代,元空间使用本地内存而非 JVM 内存

私有部分

  • PC 寄存器:保存当前线程正在执行的方法地址
  • Java 虚拟机栈:存储栈帧,每个栈帧包含局部变量表、操作数栈、动态连接、方法返回地址等信息
  • 本地方法栈:管理本地方法(Native Method)的调用

1.5 类加载机制

类加载过程分为七个阶段:加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载

  • 加载:将字节码数据加载到内存
  • 验证:校验 Class 文件格式是否符合 JVM 规范
  • 准备:为 static 变量分配内存并赋予默认零值(final 修饰的赋实际值)
  • 解析:将常量池中的符号引用替换为直接引用
  • 初始化:按语句顺序执行类初始化(static 变量赋值、static 代码块),有父类先初始化父类。整体是惰性初始化
  • 使用:JVM 从入口方法开始执行程序
  • 卸载:销毁 Class 对象,JVM 退出内存

1.6 垃圾回收(GC)

判断对象存活

  • 引用计数法(已淘汰):对象被引用时 +1,取消引用时 -1。缺陷:无法解决循环引用
  • 可达性分析(GC Root Tracing):从 GC Root 出发,可达的对象存活,不可达的为垃圾

GC Root 主要包括:

  • 虚拟机栈中引用的对象
  • 类静态属性引用的对象
  • 常量引用的对象
  • 本地方法栈中 JNI 引用的对象

四种引用类型

引用类型 回收时机 使用场景
强引用(Strong Reference) 永不回收 Object obj = new Object()
软引用(Soft Reference) 内存不足时回收 缓存
弱引用(Weak Reference) GC 时即回收 ThreadLocal
虚引用(Phantom Reference) 任何时候都可能 跟踪对象回收通知

垃圾回收算法

算法 过程 优缺点 适用场景
标记清除 标记存活对象,清除未标记的 会产生内存碎片 老年代
复制算法 将存活对象复制到另一块区域,清空当前区域 内存利用率只有一半 新生代(存活对象少)
标记压缩 标记存活对象,移动到内存一端,清空剩余区域 无碎片,但耗时 老年代

GC 术语

  • Minor GC / Young GC:从年轻代回收内存
  • Major GC / Old GC:从老年代回收内存
  • Full GC:清理年轻代、老年代和元空间
  • Stop-The-World:GC 时暂停所有用户线程

垃圾回收器

回收器 类型 特点
Serial 串行 单线程,Stop-The-World
ParNew 并行(新生代) Serial 的多线程版本
Parallel Scavenge 并行(新生代) 注重吞吐量
Parallel Old 并行(老年代) 注重吞吐量
CMS(Concurrent Mark Sweep) 并发(老年代) 注重低停顿,工作流程:初始标记→并发标记→并发预清理→重新标记→并发清除
G1(Garbage First) 并发 JDK 9+ 默认,分区回收,优先回收价值最大的 Region

JDK 1.8 默认:Parallel Scavenge(新生代)+ Parallel Old(老年代)

1.7 JVM 常用参数

堆栈配置

参数 含义
-Xms 初始堆大小
-Xmx 最大堆空间
-Xmn 新生代大小
-XX:SurvivorRatio Eden 和 Survivor 的比例
-XX:MetaspaceSize 元空间 GC 阈值(JDK 8+)
-XX:MaxMetaspaceSize 最大元空间大小(JDK 8+)
-Xss 栈大小

跟踪监控

参数 含义
-XX:+PrintGC 打印 GC 日志
-XX:+PrintGCDetails 打印详细 GC 日志
-verbose:class 跟踪类的加载和卸载
-XX:+TraceClassLoading 跟踪类的加载

常用 JDK 监控工具

  • jps:列出所有 Java 进程
  • jstat:监视堆信息(如 jstat -gcutil <pid>
  • jinfo:查看运行中程序的扩展参数
  • jmap:生成 Dump 文件,查看堆内对象统计
  • jhat:分析堆快照
  • jstack:查看线程状态,可检测死锁
  • jconsole:图形化监控工具

二、Java集合框架

2.1 整体架构

Java 集合框架主要分为两大接口体系:

  • Collection 接口:单列集合,存储单个元素
    • List:有序、可重复 → ArrayListLinkedListVector
    • Set:无序、不可重复 → HashSetTreeSetLinkedHashSet
    • Queue:队列 → LinkedListPriorityQueueArrayDeque
  • Map 接口:双列集合,存储键值对 → HashMapTreeMapLinkedHashMapHashtable

2.2 常用集合类

集合类 底层结构 线程安全 特点
ArrayList 动态数组 查询快,增删慢
LinkedList 双向链表 增删快,查询慢,可实现队列/栈
HashSet HashMap 无序,元素唯一
TreeSet 红黑树 有序(自然排序或比较器排序)
HashMap 数组+链表+红黑树(JDK 8+) 键值对,key 不重复,支持 null 键
TreeMap 红黑树 key 有序

2.3 HashMap 原理

  • JDK 7:数组 + 链表,采用头插法,扩容时可能导致循环链表(死链)
  • JDK 8:数组 + 链表 + 红黑树,采用尾插法。当链表长度 ≥ 8 且数组长度 ≥ 64 时,链表转为红黑树;当红黑树节点 ≤ 6 时转回链表
  • 默认初始容量 16,负载因子 0.75,扩容时容量翻倍

三、Java文件IO

3.1 按字符读写(Reader / Writer)

适用于处理文本文件。

1
2
3
4
5
6
7
8
9
10
11
12
// 写入
FileWriter writer = new FileWriter(file); // 覆盖写
FileWriter writer = new FileWriter(file, true); // 追加写
writer.write("内容");
writer.flush();
writer.close();

// 读取
FileReader reader = new FileReader(file);
char[] ch = new char[100];
reader.read(ch);
reader.close();

配合 BufferedWriter / BufferedReader 可逐行读写:

1
2
3
4
5
6
7
8
9
10
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
bw.write("内容");
bw.close();

BufferedReader br = new BufferedReader(new FileReader(file));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();

3.2 按字节读写(InputStream / OutputStream)

适用于处理所有类型文件(文本、图片、视频等)。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 写入
FileOutputStream fos = new FileOutputStream(file); // 覆盖写
FileOutputStream fos = new FileOutputStream(file, true); // 追加写
fos.write("内容".getBytes());
fos.close();

// 读取
FileInputStream fis = new FileInputStream(file);
byte[] bys = new byte[100];
while (fis.read(bys, 0, bys.length) != -1) {
System.out.println(new String(bys));
}
fis.close();

四、Java反射

反射(Reflection)允许程序在运行期获取任意一个对象的所有信息,包括类名、方法、字段等。

4.1 Class 类

  • 每加载一个类,JVM 会创建一个唯一的 Class 对象与之关联
  • Class 对象包含了 .class 文件的完整信息
  • .class 是懒加载的

获取 Class 对象的三种方式

1
2
3
4
5
6
7
8
9
10
11
12
// 1. 直接通过类名
Class cls1 = String.class;

// 2. 通过实例
String str = "hello";
Class cls2 = str.getClass();

// 3. 通过完整类名(常用于动态加载)
Class cls3 = Class.forName("java.lang.String");

// 三者在 JVM 中是同一个对象
cls1 == cls2 && cls2 == cls3; // true

获取类信息

1
2
3
4
5
6
7
8
9
cls.getName()          // 完整类名(包名+类名)
cls.getSimpleName() // 类名
cls.getPackage() // 所在包
cls.isInterface() // 是否接口
cls.isEnum() // 是否枚举
cls.isArray() // 是否数组
cls.isPrimitive() // 是否基本类型
cls.getSuperclass() // 父类
cls.getInterfaces() // 实现的接口(不含父类)

4.2 操作字段(Field)

1
2
3
4
5
6
7
// 获取字段(getField 含父类 public,getDeclaredField 不含父类)
Field f = cls.getDeclaredField("name");
f.setAccessible(true); // 访问私有字段需设为 true

// 获取/修改字段值
Object value = f.get(obj);
f.set(obj, newValue);

4.3 操作方法(Method)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 获取方法
Method m = String.class.getMethod("substring", int.class);

// 调用普通方法
String r = (String) m.invoke(s, 6); // s.substring(6)

// 调用静态方法(第一个参数传 null)
Method m = Integer.class.getMethod("parseInt", String.class);
Integer n = (Integer) m.invoke(null, "12345");

// 调用私有方法需 setAccessible(true)
Method m = obj.getClass().getDeclaredMethod("privateMethod", ...);
m.setAccessible(true);
m.invoke(obj, args...);

4.4 操作构造方法(Constructor)

1
2
3
// 获取构造方法
Constructor cons = Integer.class.getConstructor(int.class);
Integer n = (Integer) cons.newInstance(123);

五、Java注解

注解(Annotation)是给代码贴的标签/标记,本身不做任何事,只携带信息,由解析器(反射/AOP/框架)读取并执行逻辑。

5.1 元注解

用于注解其他注解的注解:

元注解 说明
@Target 指定注解可以用在什么地方(TYPE、METHOD、FIELD 等)
@Retention 指定注解保留到什么时候:SOURCE(源码阶段)、CLASS(编译阶段)、RUNTIME(运行期,默认)
@Documented 生成 javadoc 时保留注解信息
@Inherited 使子类能继承父类的注解

5.2 自定义注解示例

1
2
3
4
5
6
7
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLog {
String value() default "";
boolean enable() default true;
}

5.3 通过 AOP 解析注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Aspect
@Component
public class MyLogAspect {
@Pointcut("@annotation(com.xw.annotation.MyLog)")
public void pointcut() {}

@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
MyLog myLog = signature.getMethod().getAnnotation(MyLog.class);
// 读取注解的值执行业务逻辑
String value = myLog.value();
return joinPoint.proceed();
}
}

六、Java代理

代理模式:给某一个对象提供一个代理,并由代理对象控制对真实对象的访问。

角色:Subject(接口)、RealSubject(被代理对象)、Proxy(代理对象)

6.1 静态代理 vs 动态代理

类型 特点
静态代理 运行前确定代理关系,代理类字节码已生成。优点:简单、不侵入原代码。缺点:代理类数量多、修改目标类时需同步修改代理类
动态代理 运行时通过反射动态生成代理类字节码。常见的实现有 JDK 动态代理和 CGLIB 动态代理

6.2 JDK 动态代理

基于接口创建代理类:

  • 使用 java.lang.reflect.InvocationHandler 定义代理逻辑
  • 使用 java.lang.reflect.Proxy 创建代理对象
  • 代理对象通过反射获取方法,然后交给 InvocationHandler 执行

6.3 CGLIB 动态代理

基于继承创建代理类:

  • 通过生成目标类的子类来实现代理
  • 缺点:需要额外引入 CGLIB 包;只能代理非 final 的 public 方法

七、Java线程

7.1 线程生命周期

线程有六种状态:

  • NEW:线程正在创建
  • RUNNABLE:线程可以运行(可能在等待时间片或正在运行)
  • BLOCKED:线程等待获取锁
  • WAITING:当前线程等待其他线程唤醒
  • TIMED_WAITING:当前线程等待一段时间后自动醒来
  • TERMINATED:线程执行完毕或出现异常

7.2 线程创建方式

Java 中有三种创建线程的接口/类:

方式一:继承 Thread 类

1
2
3
4
Thread thread = new Thread(() -> {
System.out.println("线程执行");
});
thread.start();

方式二:实现 Runnable 接口

1
2
Thread thread = new Thread(new MyRunnable());
thread.start();

优点:任务与线程解耦,更容易与线程池配合。

方式三:实现 Callable 接口

1
2
3
4
FutureTask<Integer> task = new FutureTask<>(new MyCallable());
Thread thread = new Thread(task);
thread.start();
Integer result = task.get(); // 可获取返回值

优点:任务可以有返回值,可以抛出异常。

7.3 线程常用 API

方法 说明
Thread.sleep(n) 当前线程休眠 n 毫秒,进入 TIMED_WAITING 状态,不释放锁
Thread.yield() 建议让出 CPU,由调度器决定
thread.join() 等待调用线程执行完毕后再继续
thread.interrupt() 中断线程:睡眠中的线程会抛 InterruptedException;运行中的线程会设置中断标记
thread.isInterrupted() 检查中断标记(不清除)
Thread.interrupted() 检查中断标记(清除)
thread.setDaemon(true) 设置为守护线程,主线程结束时守护线程自动终止
obj.wait() 挂起线程,释放锁,只能在 synchronized 块中调用
obj.notify() / obj.notifyAll() 唤醒等待线程,只能在 synchronized 块中调用

sleep vs wait 的区别:sleep 抱着锁睡觉(不释放锁),wait 会释放锁。

7.4 线程互斥与协作

互斥方式

  • synchronized 关键字
  • Lock 接口及其实现类

协作方式

  • join():等待线程结束
  • wait() / notify() / notifyAll():基于 Object 监视器
  • await() / signal() / signalAll():基于 Condition 条件队列
  • LockSupport.park() / LockSupport.unpark():更底层的阻塞/唤醒

7.5 线程池

为什么使用线程池

对线程进行统一分配、调优和监控,避免频繁创建销毁线程的开销。

ThreadPoolExecutor 核心参数

1
2
3
4
5
6
public ThreadPoolExecutor(int corePoolSize,          // 核心线程数
int maximumPoolSize, // 最大线程数 = 核心 + 救急
long keepAliveTime, // 救急线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
RejectedExecutionHandler handler) // 拒绝策略

线程池状态

通过原子整数 ctl 的高 3 位记录状态,低 29 位记录线程数:

状态 接收新任务 处理队列任务
RUNNING Y Y
SHUTDOWN N Y
STOP N N
TIDYING - -
TERMINATED - -

常用阻塞队列

队列类型 特点
ArrayBlockingQueue 基于数组的有界阻塞队列,FIFO
LinkedBlockingQueue 基于链表的阻塞队列(可无界),吞吐量较高
SynchronousQueue 不存储元素,插入必须等移除操作
PriorityBlockingQueue 有优先级的无界阻塞队列

拒绝策略

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

Executors 提供的线程池

类型 特点 注意事项
newFixedThreadPool(n) 固定大小线程池,无救急线程 队列无界,可能导致 OOM
newCachedThreadPool() 全是救急线程(60s 存活),遇任务创建 线程数无上限,可能导致 OOM
newSingleThreadExecutor() 单线程池,任务串行执行 故障后会新建线程保证运行
newScheduledThreadPool(n) 支持定时和延时任务 任务执行时间长时会延后下一个任务

为什么不建议使用 Executors 创建线程池? 因为它使用了无界队列或无限线程数的配置,可能导致内存溢出(OOM)。推荐直接使用 ThreadPoolExecutor 构造函数指定具体参数。


八、Java锁

8.1 锁的分类

Java 中的锁分为两类:

  1. synchronized 关键字 —— JVM 实现,隐式锁,自动获取和释放
  2. Lock 接口及其实现类 —— 显式锁,需手动 lock()unlock()

8.2 synchronized

使用方式

1
2
3
4
5
6
7
8
// 1. 实例方法——锁的是 this
public synchronized void method() { }

// 2. 静态方法——锁的是类对象(Class 对象)
public static synchronized void method() { }

// 3. 同步代码块——锁指定对象
synchronized (obj) { }

原理

  • 方法级的 synchronized:通过方法标志 ACC_SYNCHRONIZED 实现
  • 代码块级的 synchronized:通过 monitorentermonitorexit 指令实现
  • 每个 Java 对象可以关联一个 Monitor(管程/监视器),Monitor 只能由一个线程拥有
  • 其他线程进入 EntryList 阻塞队列等待
  • Owner 线程调用 wait() 后进入 WaitSet,释放 Monitor

存在的问题

Monitor 的实现依赖操作系统底层的互斥锁(mutex),需要用户态和内核态的切换,效率较低。

8.3 锁升级机制

JVM 通过多级锁优化来解决 synchronized 的性能问题,锁只能升级不能降级:

无锁 → 偏向锁 → 轻量级锁 → 重量级锁

锁类型 原理 适用场景
偏向锁 Mark Word 记录偏向的线程 ID,同一线程重入无需 CAS 单线程访问
轻量级锁 CAS 交换 Mark Word 和锁记录,失败则自旋重试 多线程错开访问
重量级锁 依赖 Monitor,线程阻塞 多线程竞争激烈
  • 锁膨胀:轻量级锁 CAS 失败且有竞争时,升级为重量级锁
  • 自旋优化:竞争时不立即阻塞,先自旋重试几次(适合多核 CPU)
  • 偏向锁撤销:发生竞争时撤销偏向锁。批量重偏向阈值 20 次,批量撤销阈值 40 次

8.4 按性质分类

分类维度 类型 说明
加锁策略 悲观锁 每次操作都加锁(如 synchronized)
乐观锁 通过 CAS 尝试修改,不阻塞(如 AtomicInteger)
获取顺序 公平锁 FIFO 先来后到
非公平锁 抢占式,synchronized 属于非公平锁
持有数量 独占锁 只能被一个线程持有
共享锁 可被多个线程持有(如读锁)

8.5 Lock 接口

ReentrantLock

可重入锁,支持公平/非公平(默认非公平),相比于 synchronized 的特点:

  • 可中断lockInterruptibly()
  • 可超时tryLock(long timeout, TimeUnit unit)
  • 可设为公平锁new ReentrantLock(true)
  • 支持多个条件变量lock.newCondition() 创建多个等待队列
1
2
3
4
5
6
7
Lock lock = new ReentrantLock();
lock.lock();
try {
// 业务代码
} finally {
lock.unlock(); // 必须在 finally 中释放
}

可重入实现:每次获取锁 state++,释放时只有 state == 0 才真正释放锁。

8.6 AQS(AbstractQueuedSynchronizer)

AQS 是实现锁(ReentrantLock、ReentrantReadWriteLock 等)的基础框架。

核心组成

  • state(int 类型):表示同步状态(0=未锁,1=已锁)
  • FIFO 等待队列(CLH 队列):节点分为独占式和共享式
  • 条件变量:支持多个等待队列

主要方法

分类 方法 说明
状态操作 getState() / setState() / compareAndSetState() 获取/设置/CAS 设置状态
独占式 tryAcquire() / tryRelease() 子类重写,实现获取/释放逻辑
共享式 tryAcquireShared() / tryReleaseShared() 子类重写,实现共享获取/释放
模板方法 acquire() / release() / acquireShared() / releaseShared() 提供标准流程,调用 tryXxx

8.7 ReentrantReadWriteLock

读写锁,基于 ReentrantLock 和 AQS 实现:

  • 读-读并发读-写互斥写-写互斥
  • state 高 16 位表示读锁数量,低 16 位表示写锁重入次数
  • 有写锁时可直接获取读锁(锁降级);有读锁时不能直接获取写锁

8.8 StampedLock

JDK 8 新增的乐观读写锁,基于”戳”(stamp)判断数据是否被修改:

  • tryOptimisticRead() 获取乐观读戳
  • validate(stamp) 校验戳是否通过
  • 如果校验失败,升级为读锁

注意:StampedLock 不支持条件变量,不支持可重入。适用于读多写少的场景。


九、Java并发编程

9.1 基础概念

概念 说明
进程 程序的一个实例,资源分配的最小单位
线程 进程内的指令流,CPU 调度的最小单位
并发 单 CPU 交替执行多条指令
并行 多 CPU 同时执行指令
同步 等待结果返回后才继续执行
异步 不等结果,继续执行后续代码

9.2 共享问题

临界区与竞态条件

  • 临界区:存在对共享资源多线程读写的代码块
  • 竞态条件:多线程在临界区内执行,因执行序列不同导致结果无法预测

解决方式:

  • 阻塞式:synchronized、Lock
  • 非阻塞式:原子变量(基于 CAS)

变量的线程安全

  • 成员变量/静态变量:共享且读写 → 线程不安全
  • 局部变量:引用常量 → 安全;引用对象逃逸到方法外 → 不安全

9.3 JMM(Java 内存模型)

JMM 定义了主存、工作内存的抽象概念,体现在三个方面:

特性 说明 保证方式
原子性 指令不受线程上下文切换影响 synchronized
可见性 一个线程对共享变量的修改对其他线程立即可见 volatile、synchronized
有序性 指令按顺序执行(避免指令重排影响多线程正确性) volatile

volatile 关键字

  • 保证可见性:每次读取都从主内存获取最新值
  • 禁止指令重排:通过内存屏障实现
    • 写屏障:在该屏障之前的写操作都同步到主存
    • 读屏障:在该屏障之后读取的都是主存最新数据
  • 不能保证原子性

9.4 乐观锁与 CAS

CAS(Compare And Swap)

不断尝试比较当前值与预期值,相同则修改,不同则重试:

1
2
3
4
5
6
7
8
AtomicInteger balance = new AtomicInteger(100);
while (true) {
int prev = balance.get();
int next = prev - amount;
if (balance.compareAndSet(prev, next)) {
break;
}
}

CAS 必须配合 volatile 使用才能读取到共享变量的最新值。

优点:无锁并发,无线程上下文切换开销(适合线程数少、多核 CPU)

缺点:竞争激烈时重试频繁,可能效率低于有锁

ABA 问题

线程 T2 将值 A→B→A,线程 T1 的 CAS 认为值没变,但实际语义已改变。

解决方案

  • AtomicStampedReference:加版本号(类似乐观锁)
  • AtomicMarkableReference:只关心是否被修改过(布尔标记)

JUC 原子类

类别 类名
原子整数 AtomicIntegerAtomicLongAtomicBoolean
原子引用 AtomicReferenceAtomicStampedReference
原子数组 AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray
字段更新器 AtomicIntegerFieldUpdaterAtomicReferenceFieldUpdater
原子累加器 LongAdderDoubleAdder(高并发下性能优于 AtomicLong)

9.5 ThreadLocal

线程本地变量,每个线程都有变量的独立副本,实现线程隔离

  • 创建:new ThreadLocal<>()
  • 存储:set(value)
  • 获取:get()
  • 清理:remove()

原理:每个线程有一个 ThreadLocalMap,以 ThreadLocal 的弱引用为 key 存储值。哈希冲突时使用开放定址法解决。

内存泄漏风险:ThreadLocal 的 key 是弱引用,GC 时会被回收,但 value 不会。解决方式:使用完毕后调用 remove()

9.6 活跃性问题

问题 说明
死锁 多个线程互相等待对方持有的资源,全部阻塞
活锁 线程互相改变对方的结束条件,都无法结束但仍在运行
饥饿 线程优先级太低,始终得不到 CPU 调度

死锁的四个必要条件(破坏任一即可避免):

  1. 互斥:资源只能给一个线程使用
  2. 不可剥夺:资源只能由持有者释放
  3. 请求和保持:持有资源的同时申请其他资源
  4. 循环等待:形成等待环路

定位死锁jstack <pid> 会显示 Found one Java-level deadlock:,或用 jconsole 图形化检测。

9.7 DCL(Double-Checked Locking)单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final class Singleton {
private Singleton() { }

// volatile 防止指令重排导致返回未初始化完成的对象
private static volatile Singleton INSTANCE = null;

public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}

其他单例实现方式:

  • 饿汉式private static final Singleton INSTANCE = new Singleton();(类加载时创建)
  • 枚举enum Singleton { INSTANCE }(天然单例,防反射、防序列化)
  • 静态内部类:利用类加载机制的懒汉式,线程安全

9.8 JUC 并发工具

Semaphore(信号量)

限制同时访问共享资源的线程数量:

1
2
3
4
Semaphore semaphore = new Semaphore(3);  // 最多 3 个线程同时运行
semaphore.acquire(); // 获取信号量
// ... 业务代码
semaphore.release(); // 释放信号量

CountDownLatch(倒计数门闩)

等待所有线程完成任务后继续执行(一次性):

1
2
3
CountDownLatch latch = new CountDownLatch(3);
// 各线程完成任务后 latch.countDown();
latch.await(); // 等待 count 降为 0

与 join 相比,CountDownLatch 可以配合线程池使用。

CyclicBarrier(循环栅栏)

与 CountDownLatch 类似,但可以循环使用

1
2
3
4
CyclicBarrier barrier = new CyclicBarrier(2, () -> {
// 所有线程到齐后执行的回调
});
barrier.await(); // 等待其他线程到齐

ForkJoinPool

JDK 1.7 引入,基于分治思想,适用于 CPU 密集型计算:

  • 将大任务拆分为小任务,多线程并行计算,最后合并结果
  • 核心类:RecursiveTask(有返回值)、RecursiveAction(无返回值)

9.9 ConcurrentHashMap

JDK 7 vs JDK 8

版本 实现 问题
JDK 7 Segment 分段锁(继承 ReentrantLock) 并发度固定
JDK 8 数组+链表+红黑树,CAS + synchronized 更高的并发度

JDK 8 原理

  • 懒惰初始化:构造时只计算容量,首次使用时才创建数组
  • get:无需加锁,使用 volatile 保证可见性
  • put:CAS 尝试创建节点/插入头结点,需要时用 synchronized 锁住链表/红黑树
  • 扩容:发现其他线程正在扩容(hash 为 -1)时,当前线程会协助扩容
  • size 计算:无竞争时累加 baseCount,有竞争时分散到 CounterCell 数组,最后求和

9.10 并发编程模式

保护性暂停(Guarded Suspension)

一个线程等待另一个线程的执行结果(同步模式):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class GuardedObject {
private Object response;

public Object get() {
synchronized (this) {
while (response == null) {
this.wait();
}
}
return response;
}

public void complete(Object response) {
synchronized (this) {
this.response = response;
this.notifyAll();
}
}
}

使用 while 而非 if 判断条件,防止虚假唤醒问题。


Java基础知识总结
http://xwww12.github.io/2026/06/16/编程语言/java/Java基础知识总结/
作者
xw
发布于
2026年6月16日
许可协议