java中真正被cpu调度执行的是线程,进程仅作为资源容器;线程共享进程堆、文件句柄等资源,仅独占栈和程序计数器,故切换开销小但需处理竞态;单核下仅支持并发(时间片轮转),多核才支持并行(物理同时执行)。

进程和线程到底谁在“干活”?
Java里真正被CPU调度执行的是线程,不是进程。一个Java程序启动后是一个进程(比如 java -jar app.jar),但它默认至少有一个线程(main 线程);你用 new Thread() 或 ExecutorService 开的,都是这个进程里的新线程。
关键区别在于资源:进程有独立的堆、文件句柄、JVM内存空间;线程只独占自己的栈和程序计数器,其余都共享进程资源——所以线程切换快、开销小,但共享也意味着必须处理竞态问题。
- 别误以为“开了多个线程就等于开了多个JVM”,那是多个进程
- 用
jps -l查看到的是进程;用jstack <pid></pid>才能看到里面所有线程状态 - 线程崩溃(如未捕获异常)一般不会杀掉整个进程,但可能导致资源泄漏或状态不一致
单核CPU上能“并发”,但绝不可能“并行”
并发(concurrent)是宏观上“看起来一起跑”,本质是操作系统把CPU时间片(Windows约15ms,Linux更短)轮流分给不同线程;并行(parallel)是微观上“真正在同时跑”,必须有多核物理支持。
Java代码写得再“多线程”,在单核机器上永远只是并发——Thread.sleep(1)、Object.wait()、I/O阻塞时,调度器就会切走CPU去跑别的线程。这不是Java的限制,是硬件决定的。
立即学习“Java免费学习笔记(深入)”;
-
Runtime.getRuntime().availableProcessors()返回的是逻辑核数,不是“保证可用”的并发能力 - 不要靠“线程数 = CPU核数”硬配线程池,还要看任务类型:CPU密集型用
N,IO密集型常用2N甚至更高 - 用
VisualVM或JFR观察Thread State,如果大量线程长期处于WAITING或BLOCKED,说明不是CPU瓶颈,而是锁或IO拖慢了
synchronized 和 wait/notify 的坑,90%人栽在唤醒逻辑上
用 synchronized 加锁后调 wait(),不是“暂停自己等信号”,而是“释放锁 + 进入等待队列”;notify() 不保证唤醒哪个线程,notifyAll() 才是安全选择——尤其在多个条件共用一把锁时。
典型翻车场景:三个线程轮着打印数字,有人写 if (count % 3 != index) wait(),但被唤醒后没再检查条件,直接往下执行,导致跳号或重复打印。
- 必须用
while而不是if包裹wait(),防止虚假唤醒(spurious wakeup) -
wait()必须在synchronized块内调用,否则抛IllegalMonitorStateException -
sleep()不释放锁,wait()会释放——这是二者最根本的行为差异,别混用
多核CPU下,并行≠自动高效
有了8核,不代表8个线程就能跑满800% CPU。Java线程映射到OS线程,最终由内核调度;如果线程频繁争抢同一把锁、或反复GC导致Stop-The-World,再多核也白搭。
真实瓶颈常藏在看不见的地方:HashMap扩容时的锁竞争、日志框架的同步刷盘、甚至JIT编译过程中的去优化(deoptimization)。
- 用
-XX:+PrintGCDetails看GC是否拖累吞吐;用-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly(需hsdis)查热点方法是否被内联 - 避免在高并发路径上用
String.substring()(老版本JDK中可能共享底层数组引发内存泄漏) - 线程局部变量优先用
ThreadLocal,但记得手动remove(),尤其在线程池场景下,否则可能引发内存泄漏









