




















java.util.concurrent 全景解析面向刚接触并发编程的开发者,一篇从历史、思想到实战的入门技术博客。
很多初学者第一次接触并发编程时,都会有一个疑问:
“单线程不是已经能工作了吗?为什么还需要多线程?”
答案很简单:
现代软件系统越来越需要:
例如:
| 场景 | 为什么需要并发 |
|---|---|
| Web 服务 | 同时处理多个用户请求 |
| 数据库连接池 | 多线程复用连接 |
| 消息队列 | 异步消费消息 |
| 游戏服务器 | 同时处理多个玩家 |
| GUI 程序 | 避免界面卡死 |
| AI / 大数据 | 并行计算 |
而 Java,从诞生开始,就把并发视为语言核心能力之一。
Java 并发的发展,大致可以分为六个阶段。
Java 在 1995 年发布时,就内置了:
Threadsynchronizedwait/notify这是当时很多语言都没有的能力。
class MyTask extends Thread {
@Override
public void run() {
System.out.println("Hello Thread");
}
}
public class Main {
public static void main(String[] args) {
new MyTask().start();
}
}
或者:
new Thread(() -> {
System.out.println("Hello");
}).start();
public synchronized void increment() {
count++;
}
synchronized 本质上是:
JVM 内置的互斥锁(Monitor Lock)
它可以保证:
虽然 Java 很早支持多线程,但早期 API 非常原始。
开发者需要手动处理:
代码复杂且容易出错。
例如:
synchronized (lock) {
while (!condition) {
lock.wait();
}
// do something
lock.notifyAll();
}
很多人第一次看到 wait/notify 都会怀疑人生。
因为:
于是:
Java 社区开始探索更高级的并发抽象。
随着多核 CPU 出现,一个严重问题开始浮现:
多线程下,变量为什么会“莫名其妙”出错?
例如:
class Example {
boolean ready = false;
void writer() {
ready = true;
}
void reader() {
if (ready) {
System.out.println("OK");
}
}
}
理论上:
writer() 执行后,reader() 应该看到 ready=true。
但实际上:
可能看不到。
原因包括:
于是 Java 引入:
JMM 定义了:
volatile boolean ready;
volatile 保证:
但它:
例如:
volatile int count;
count++;
仍然不是线程安全的。
java.util.concurrent(JDK 1.5)革命这是 Java 并发历史上最重要的一次升级。
2004 年,JDK 1.5 发布。
Doug Lea 主导设计了:
java.util.concurrent简称:
它彻底改变了 Java 并发编程。
JUC 最大的贡献是:
用“高层抽象”替代底层线程操作。
也就是说:
开发者不再需要:
而是直接使用成熟组件。
这和现代 Python / Go / Rust 的并发思想非常接近。
JUC 可以分成几个核心模块:
java.util.concurrent
├── Executor(线程池)
├── Locks(锁)
├── Atomic(原子类)
├── Collections(并发集合)
├── Synchronizers(同步器)
├── ForkJoin(分治并行)
├── Future / CompletableFuture(异步编程)
└── Flow(响应式流)
下面逐个解析。
这是 JUC 最重要的模块之一。
早期代码:
new Thread(task).start();
问题:
线程池的思想:
线程复用。
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> {
System.out.println(Thread.currentThread().getName());
});
核心接口:
| 接口 | 作用 |
|---|---|
| Executor | 执行任务 |
| ExecutorService | 管理线程池 |
| ScheduledExecutorService | 定时任务 |
实际上:
Executors.newFixedThreadPool()
底层就是:
ThreadPoolExecutor
其核心参数:
ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
)
初学者重点理解:
| 参数 | 含义 |
|---|---|
| corePoolSize | 核心线程数 |
| maximumPoolSize | 最大线程数 |
| workQueue | 任务队列 |
| handler | 拒绝策略 |
提交任务
↓
核心线程满了吗?
↓
否 → 创建核心线程
是
↓
队列满了吗?
↓
否 → 进入队列
是
↓
达到最大线程数了吗?
↓
否 → 创建临时线程
是
↓
触发拒绝策略
很多教程会这样写:
Executors.newFixedThreadPool(100)
但阿里 Java 规范不推荐。
因为:
默认队列可能无界。
可能导致:
生产环境通常手动创建:
new ThreadPoolExecutor(...)
虽然 synchronized 很方便。
但它功能有限。
于是 JUC 引入:
java.util.concurrent.locks
Lock lock = new ReentrantLock();
lock.lock();
try {
// critical section
} finally {
lock.unlock();
}
相比 synchronized:
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 自动释放锁 | 是 | 否 |
| 可中断 | 否 | 是 |
| 超时获取锁 | 否 | 是 |
| 公平锁 | 否 | 是 |
| 条件变量 | 弱 | 强 |
Condition 类似:
wait / notify 的升级版
Condition condition = lock.newCondition();
condition.await();
condition.signal();
它比 wait/notify 更清晰。
思想:
读共享,写互斥
ReadWriteLock rwLock = new ReentrantReadWriteLock();
适合:
进一步优化读性能。
支持:
适合高并发读场景。
这是很多人第一次接触“无锁编程”。
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
为什么它线程安全?
因为底层使用:
思想:
如果当前值还是预期值
那么就更新
否则重试
相比锁:
A → B → A
值虽然没变。
但过程变了。
解决方案:
AtomicStampedReference
LongAdder adder = new LongAdder();
高并发下比 AtomicLong 更快。
思想:
分段累加
减少竞争
这是现代高性能计数器经典设计。
普通集合:
ArrayList
HashMap
都不是线程安全的。
JUC 最经典组件之一。
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
它经历了两代设计:
| JDK | 实现 |
|---|---|
| JDK 7 | Segment 分段锁 |
| JDK 8 | CAS + synchronized |
思想:
写时复制
写操作:
适合:
这是生产者消费者模型核心。
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
常见实现:
| 类 | 特点 |
|---|---|
| ArrayBlockingQueue | 有界数组队列 |
| LinkedBlockingQueue | 链表队列 |
| PriorityBlockingQueue | 优先级队列 |
| DelayQueue | 延迟队列 |
| SynchronousQueue | 不存储元素 |
很多线程池底层都依赖 BlockingQueue。
这是 JUC 非常强大的设计。
CountDownLatch latch = new CountDownLatch(3);
作用:
等待多个任务完成
例如:
让多个线程互相等待
类似:
“大家到齐再出发”
Semaphore semaphore = new Semaphore(3);
作用:
限制并发数量
例如:
JDK 7 引入。
更灵活的阶段同步器。
适合复杂并行流程。
现代 CPU 已经是多核。
于是 Java 引入:
核心思想:
分而治之
例如:
大任务
→ 拆分小任务
→ 并行执行
→ 合并结果
这是 ForkJoin 最大创新。
空闲线程
去偷其他线程任务
提升 CPU 利用率。
class SumTask extends RecursiveTask<Long> {
// compute()
}
后来:
Java Stream 并行流底层也基于 ForkJoinPool。
list.parallelStream()
这是 Java 异步编程演进史。
Future<Integer> future = pool.submit(() -> 1 + 2);
Integer result = future.get();
问题:
get() 会阻塞
无法优雅组合异步任务。
革命性升级。
CompletableFuture
.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenAccept(System.out::println);
它支持:
很多前端开发者会发现:
CompletableFuture 很像 Promise
确实如此。
例如:
| Java | JavaScript |
|---|---|
| thenApply | then |
| exceptionally | catch |
| allOf | Promise.all |
JDK 9 引入:
java.util.concurrent.Flow
实现 Reactive Streams 标准。
核心思想:
异步数据流
+ 背压(Backpressure)
适合:
不过:
实际生产中更常见的是:
这是近年来 Java 并发最大变革。
传统线程:
1 个线程 ≈ 1 个操作系统线程
成本很高。
大量线程会:
JDK 21 正式引入:
Thread.startVirtualThread(() -> {
System.out.println("Hello Virtual Thread");
});
思想类似:
特点:
过去 Java 高并发依赖:
异步回调
代码复杂。
虚拟线程让开发者:
重新写“同步代码”
却获得高并发能力
这可能改变未来 Java 服务端编程方式。
学习并发,最重要的是理解下面几个概念。
操作不可分割。
例如:
count++
实际上不是原子操作。
它包括:
读取
修改
写回
一个线程修改变量。
另一个线程能否立即看到?
这就是:
volatile
锁
JMM
解决的问题。
CPU 和编译器可能重排序。
导致多线程问题。
典型情况:
线程 A 等 B
线程 B 等 A
避免方法:
很多 JUC 组件底层都基于:
简称:
例如:
都基于 AQS。
state + CLH 队列 + CAS
即:
AQS 可以说是:
Java 并发框架的“发动机”
很多初学者一上来:
结果很快劝退。
正确顺序应该是:
重点:
推荐目标:
能理解:
为什么多线程会出错
重点:
推荐目标:
能够编写实际业务代码。
重点:
重点:
锁范围过大。
导致性能下降。
pool.shutdown();
很多人忘记。
可能导致 OOM。
可能死循环。
应使用:
ConcurrentHashMap
它不保证复合操作原子性。
除了 JUC,现代 Java 并发还有很多重要框架。
| 技术 | 作用 |
|---|---|
| Netty | 高性能网络框架 |
| Reactor | 响应式编程 |
| RxJava | Reactive 扩展 |
| Akka | Actor 模型 |
| Quasar | 早期协程方案 |
| Loom | 虚拟线程 |
Java 的很多思想后来影响了整个行业。
例如:
| Java | 后来的对应思想 |
|---|---|
| Future | Promise |
| CompletableFuture | async pipeline |
| ForkJoin | work stealing |
| ConcurrentHashMap | lock striping |
| Loom | goroutine 风格 |
Java 并发生态其实非常超前。
如果你刚开始学习并发:
不要一开始就研究:
先学会:
如何正确使用并发工具
这是最重要的。
真正的成长路径是:
会用 → 理解原理 → 理解设计 → 能自己设计并发系统
Java 并发的发展史,本质上是:
从“操作线程”
到
“抽象并发”
再到
“简化并发”
的发展过程。
它经历了:
| 阶段 | 核心特征 |
|---|---|
| Thread 时代 | 手动线程管理 |
| synchronized 时代 | JVM 内置锁 |
| JUC 时代 | 高级并发组件 |
| ForkJoin 时代 | 多核并行 |
| CompletableFuture 时代 | 异步编排 |
| Loom 时代 | 轻量线程 |
而 java.util.concurrent:
则是 Java 并发体系真正成熟的标志。
它不仅提供工具。
更重要的是:
它定义了现代 Java 并发编程的方法论。
并发编程并不是 Java 独有的话题。
但 Java 是整个工业界里:
对并发体系建设最完整、最系统的语言之一。
理解 Java 并发,实际上也是在理解:
这也是为什么:
很多高级程序员最终都会回到并发原理本身。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。