Java 并发是指程序同时执行多个代码块的能力。本文档涵盖了在 Java 中编写并发代码的基础概念、机制和最佳实践。它解释了线程、同步原语、线程池、异步编程模型以及 Java 应用程序中常用的并发模式。
有关 Java 集合的信息,请参阅 Java 集合。有关 JVM 架构的信息,请参阅 JVM 架构。
线程是 Java 中并发执行的基本单元。Java 中的线程代表程序中独立的执行路径。Java 提供了多种与线程交互的方式,包括直接创建线程和使用更高级别的并发工具。
Thread 类是 Java 核心 API 的一部分,代表单个执行线程。每个线程都在单独的调用堆栈中运行,并拥有自己的程序计数器。
Java 中的线程可以处于以下任一状态:
下图说明了 Java 线程的状态转换。
来源:docs/java/concurrent/java-concurrent-questions-01.md132-162
synchronized 关键字Java 中的 synchronized 关键字用于控制多个线程对共享资源的访问。它可以应用于方法或代码块。
当使用 synchronized 时,一次只有一个线程可以访问同步块,从而实现互斥。
有三种使用 synchronized 关键字的方法:
实例方法同步:锁定当前对象实例
静态方法同步:锁定 Class 对象
代码块同步:锁定指定对象
在底层,synchronized 使用监视器(也称为内部锁或监视器锁)来实现互斥。
来源:docs/java/concurrent/java-concurrent-questions-02.md430-563
volatile 关键字Java 中的 volatile 关键字用于指示变量的值将被不同线程修改。
volatile 关键字保证:
然而,volatile 不为复合操作(如自增 i++)提供原子性。
来源:docs/java/concurrent/java-concurrent-questions-02.md22-162
ThreadLocal 提供了一种存储特定于当前线程的数据的方式,确保每个线程都有自己独立初始化的变量副本。
主要特性
ThreadLocal 变量的线程都有其独立初始化的变量副本。ThreadLocal 变量如果使用不当可能导致内存泄漏。这是因为:
Thread 中的 ThreadLocalMap 以 ThreadLocal 作为键(弱引用)和实际值(强引用)持有 Entry 对象。ThreadLocal 对象被垃圾回收,但线程仍然存活(例如在线程池中),那么值将无法被垃圾回收。最佳实践:当您不再需要 ThreadLocal 变量时,请务必调用 threadLocal.remove(),尤其是在线程池环境中。
来源:docs/java/concurrent/java-concurrent-questions-03.md17-225
线程池管理着一个工作线程池,减少了线程创建的开销。Java 提供了 ThreadPoolExecutor 类来创建和管理线程池。
ThreadPoolExecutor 类提供了一个可配置的线程池,具有几个关键参数:
| 参数 | 描述 |
|---|---|
corePoolSize | 池中要保留的线程数,即使它们是空闲的 |
maximumPoolSize | 池中允许的最大线程数 |
keepAliveTime | 当线程数大于核心线程数时,这是多余的空闲线程在终止前等待新任务的最大时间 |
workQueue | 用于在任务执行前保存任务的队列 |
threadFactory | 用于创建新线程的工厂 |
handler | 任务执行被阻塞时要使用的处理程序 |
当一个新任务提交到线程池时,会发生以下步骤:
corePoolSize,则创建一个新线程来处理该请求。corePoolSize 但少于 maximumPoolSize,则仅当队列已满时才创建一个新线程。Java 提供了四种内置拒绝策略:
RejectedExecutionException(默认)。来源:docs/java/concurrent/java-concurrent-questions-03.md232-621 docs/java/concurrent/java-thread-pool-summary.md14-136
Future 接口代表异步计算的结果,但它存在局限性:
CompletableFuture 通过提供全面的异步编程 API 来解决这些局限性。
CompletableFuture 允许链接异步操作。
使用自定义线程池:避免将通用的 ForkJoinPool 用于所有异步操作。
正确处理异常:使用 exceptionally 或 handle 方法。
组合多个 Future:使用 allOf 或 anyOf 来等待多个 Future。
来源:docs/java/concurrent/java-concurrent-questions-03.md814-952 docs/java/concurrent/completablefuture-intro.md1-33
AbstractQueuedSynchronizer (AQS) 是 Java 并发包中许多同步类的基础。AQS 为实现阻塞锁和相关同步实用程序(如信号量、倒计时锁存器等)提供了一个框架。
state 来表示同步状态。tryAcquire、tryRelease 等方法进行自定义。AQS 队列中的节点可以具有以下任一状态:
| 状态管理 | 值 | 描述 |
|---|---|---|
CANCELLED | 1 | 线程已被取消(超时或中断)。 |
SIGNAL | -1 | 当当前节点释放锁时,后继线程需要被唤醒。 |
CONDITION | -2 | 线程正在等待一个条件。 |
PROPAGATE | -3 | 共享锁的传播(确保共享获取的传播)。 |
| (initial) | 0 | 上述状态之外的任何状态。 |
来源:docs/java/concurrent/aqs.md10-102 docs/java/concurrent/java-concurrent-questions-03.md976-1021
Java 提供了各种锁实现来实现线程同步,从内部锁(synchronized)到更灵活的显式锁。
Java 支持悲观锁和乐观锁并发控制。
| 方面 | 悲观锁 | 乐观锁 |
|---|---|---|
| 假设 | 假设会发生冲突。 | 假设冲突很少发生。 |
| 实现 | synchronized, ReentrantLock | AtomicInteger, LongAdder |
| 机制 | 阻塞线程。 | 使用 CAS(比较并交换)。 |
| 性能 | 较高的冲突开销。 | 较低的冲突开销。 |
| 用例 | 高冲突场景,写密集型工作负载。 | 低冲突场景,读密集型工作负载。 |
ReentrantLock 与 synchronized 关键字相比提供了更大的灵活性。
| 功能 | ReentrantLock | Synchronized |
|---|---|---|
| 锁获取 | 显式 (lock()) | 隐式(进入代码块) |
| 锁释放 | 显式 (unlock()) | 隐式(退出代码块) |
| 公平性 | 支持公平和非公平模式。 | 始终非公平。 |
| 可中断性 | 支持 (lockInterruptibly())。 | 不支持 |
| 尝试获取锁 | 支持 (tryLock())。 | 不支持 |
| 多个条件 | 支持(通过 newCondition())。 | 不支持(每个对象只有一个等待集)。 |
| 性能 | 在现代 JVM 中与 synchronized 类似。 | 在现代 JVM 中与 ReentrantLock 类似。 |
ReentrantLock 和 synchronized 都是可重入的,这意味着持有锁的线程可以再次获取它而不会阻塞。
来源: docs/java/concurrent/java-concurrent-questions-02.md178-446 docs/java/concurrent/optimistic-lock-and-pessimistic-lock.md1-32
java.util.concurrent.atomic 包提供了支持单变量无锁线程安全编程的类。这些类通过 Compare-And-Swap (CAS) 机制使用 CPU 原子指令。
原子操作使用硬件级别的原子指令,无需锁即可确保线程安全。核心机制是 Compare-And-Swap (CAS)
对于高竞争场景,请考虑使用 LongAdder 等累加器类,而不是 AtomicLong。这些类通过维护多个计数器来减少争用,计数器仅在读取总计时进行合并。
来源: docs/java/concurrent/atomic-classes.md1-16 docs/java/concurrent/java-concurrent-questions-02.md284-326
死锁是指两个或多个线程无限期阻塞,每个线程都在等待由其他线程持有的资源。为防止死锁:
来源: docs/java/concurrent/java-thread-pool-best-practices.md8-121 docs/java/concurrent/java-concurrent-questions-01.md266-407
刷新此 Wiki
最后索引时间2025年4月17日(ff77b0)
synchronized 关键字volatile 关键字