Java并发编程核心是保障共享资源下的正确性,需直面CPU缓存、指令重排与线程切换导致的不可预测性;进程是OS内存分配单位,线程是JVM调度最小单位,共享堆内存;并发是单核时间片轮转,並行是多核真正同时执行;裸线程缺乏管理易致资源浪费与异常失控,必须用ExecutorService;synchronized保证原子性、可见性与互斥,volatile仅保证可见性与禁止重排,不保证原子性;wait/notify须用while循环重检条件以防虚假唤醒。

Java并发编程不是“多开几个线程就完事”,而是围绕「共享资源下的正确性」展开的——你写的代码在单线程下跑得通,放到多线程里很可能出错,根本原因在于:多个线程同时读写同一个变量时,CPU缓存、指令重排、线程切换这些底层机制会让结果变得不可预测。
进程、线程、并发、并行,到底谁在调度谁?
别被术语绕晕。记住这四句硬核事实:
-
进程是操作系统分配内存和文件句柄的单位,比如启动两次java -jar app.jar就是两个独立进程,互不共享堆内存; -
线程是 JVM 调度执行的最小单位,一个进程(如你的 Java 应用)默认就有main、Reference Handler、Finalizer等多个线程,它们共享同一块堆内存; -
并发是单核 CPU 上靠时间片轮转“假装同时干活”,比如 1ms 切一次线程,人眼看不出卡顿; -
并行是真·多核同时跑,比如 8 核机器上 8 个线程各自独占一个核心,但前提是你的代码没因锁或竞争被串行化。
新手常误以为“开了 10 个线程就等于快了 10 倍”,其实只要它们争抢同一把锁、频繁同步或阻塞在 I/O,实际吞吐可能比单线程还差。
为什么直接 new Thread().start() 很危险?
因为裸线程缺乏生命周期管理、复用能力和错误兜底——它就像随手打的出租车,用完就扔,既浪费资源又难追踪。
立即学习“Java免费学习笔记(深入)”;
- 每次
new Thread()都要创建栈空间(默认 1MB)、触发 JVM 线程注册、系统调用,开销远高于对象创建; - 线程异常未捕获会静默终止,
main线程不知道,监控也收不到告警; - 没有拒绝策略,突发流量可能直接
OutOfMemoryError: unable to create new native thread; - 无法统一设置守护线程、优先级、UncaughtExceptionHandler。
所以生产环境必须用 ExecutorService,哪怕只是最简单的 Executors.newFixedThreadPool(4),也比裸线程靠谱得多。
synchronized 和 volatile 到底管什么?
它们解决的是两类完全不同的问题,混用等于白加:
-
synchronized是“原子+可见+互斥”三合一:进块前加锁,出块后释放,期间其他线程进不来,且保证块内对共享变量的修改对后续进入的线程立即可见; -
volatile只管“可见性+禁止指令重排”,不保证原子性——比如counter++(读-改-写三步)用volatile修饰counter依然会丢数据; - 常见坑:
volatile boolean running = true;配合while(running)做线程开关是对的;但想用它实现计数器、状态机跳转,大概率翻车。
真正需要原子操作,请用 AtomicInteger、AtomicReference 或 ReentrantLock,别自己造轮子。
线程间通信,wait/notify 为什么总失效?
失效主因就两个字:条件判断。90% 的 bug 出在没用 while 而用 if 检查条件:
// ❌ 错误:if 可能导致虚假唤醒后直接往下走
synchronized (lock) {
if (queue.isEmpty()) {
lock.wait(); // 被唤醒后不重新检查,直接消费空队列!
}
queue.poll();
}
// ✅ 正确:必须用 while 循环重检条件
synchronized (lock) {
while (queue.isEmpty()) {
lock.wait();
}
queue.poll();
}
另外注意:wait() 必须在 synchronized 块内调用,notify() 只唤醒一个等待线程(可能不是你需要的那个),有确定顺序需求请用 notifyAll() 或更现代的 Condition。
并发编程最难的从来不是语法,而是构建“可推理的执行模型”——当你写下一行共享变量读写时,得清楚知道它在 CPU 缓存、JVM 内存模型、操作系统调度层上分别会发生什么。不理解这些,再多的 synchronized 也救不了逻辑漏洞。











