
并发(concurrency)不是同时执行,而是“看起来能同时处理多个任务”
Java 中的 concurrency 描述的是程序结构能力:多个任务交替推进、共享资源、有协作或竞争关系。它不依赖 CPU 核数,单核机器上用线程切换也能实现并发——比如 ExecutorService 提交 10 个 Runnable,实际可能只在一个核心上轮转执行。
关键点在于「调度权在 JVM 和 OS」,你写的代码要处理状态共享、可见性、原子性问题。常见错误现象包括:int count++ 在多线程下结果小于预期、HashMap 报 ConcurrentModificationException、volatile 修饰了但没解决复合操作竞态。
- 使用场景:I/O 密集型任务(如 HTTP 请求、数据库查询),线程大部分时间在等响应,此时并发能提升吞吐
- 典型工具:
ExecutorService、CompletableFuture、ReentrantLock、AtomicInteger - 不要误以为开了 10 个
Thread就等于 10 个任务并行运行——它们只是被并发调度
并行(parallelism)必须有多核,且真正同时执行
parallelism 是并发的一种物理实现方式,强调“同一时刻多个任务在不同 CPU 核心上执行”。Java 8+ 的 Stream.parallel()、ForkJoinPool 默认使用公共池(ForkJoinPool.commonPool()),其并行度通常等于 Runtime.getRuntime().availableProcessors() - 1(避免抢占主线程)。
但并行 ≠ 更快。如果任务太轻量(比如纯算术累加 100 次),线程拆分、合并、同步开销反而拖慢整体;如果数据结构不支持无锁遍历(如 ArrayList 可以,LinkedList 不适合并行流),也会退化成串行或抛异常。
立即学习“Java免费学习笔记(深入)”;
- 使用场景:CPU 密集型计算,如图像像素处理、批量 JSON 解析、数值模拟
- 注意
parallelStream()共享同一个ForkJoinPool.commonPool(),长时间阻塞(如 sleep、IO)会卡住整个池 - 想控制并行度?得自己构造
ForkJoinPool并传入stream.parallel().forEach(...)的自定义执行器
为什么 synchronized 能管并发,却不能保证并行效率
synchronized 解决的是并发下的临界区访问控制,但它本身是串行化手段:同一把锁下,所有线程必须排队进入。即使你在 8 核机器上启动 8 个线程跑带 synchronized 的方法,最终仍是一个一个执行,无法发挥并行优势。
更隐蔽的问题是锁粒度。比如对整个 ArrayList 加锁来增删元素,哪怕底层是数组,也会让所有操作互斥;而 ConcurrentHashMap 用分段锁或 CAS + 链表/红黑树迁移,允许不同桶的读写并行发生。
- 别用
synchronized(this)包裹大段逻辑,尤其是含 I/O 或远程调用的部分 -
synchronized方法默认锁实例对象,静态方法锁类对象——混淆会导致锁失效 - 考虑用
StampedLock替代读多写少场景下的synchronized,它支持乐观读,避免读线程互相阻塞
看日志或监控时,怎么判断是并发问题还是并行瓶颈
真出问题时,光看代码很难定位。优先查 Thread.getState() 快照或用 jstack 输出线程栈:
- 大量线程卡在
WAITING (on object monitor)或BLOCKED (on object monitor)→ 并发争抢锁,可能是死锁或锁竞争激烈 - 线程全在
RUNNABLE但 CPU 占用率低 → 可能是隐式阻塞(如System.in.read()、未超时的Socket.read())或 GC 频繁 - CPU 持续 100% 且线程多数为
RUNNABLE→ 真正的并行计算压满核心,需检查算法复杂度或是否误用parallelStream处理小数据集 - 用
VisualVM或JFR(Java Flight Recorder)看ForkJoinPool的 activeThreadCount / poolSize / stealCount,偷任务次数高说明负载不均
最常被忽略的一点:JVM 启动参数影响并行行为。比如 -XX:ParallelGCThreads 控制 GC 线程数,和你的业务线程共用 CPU 资源;又比如容器环境没设 -XX:+UseContainerSupport,JVM 可能按宿主机核数分配线程池,导致过载。











