18_Java 并发基础
线程任务、虚拟线程、共享状态、原子性、可见性与超时取消。
Java 并发基础
学习目标:理解任务执行、共享状态、可见性与虚拟线程,避免把并发当作无条件提速。
1. 任务与线程
并发允许多个任务在时间上交错推进;并行是同一时刻在不同计算资源上执行。I/O 等待多的任务可能从并发中受益,CPU 密集任务受核心数限制。
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) throws Exception {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<String> result = executor.submit(() -> "处理完成");
System.out.println(result.get());
}
}
}
Java 21 的虚拟线程适合大量阻塞式 I/O 任务,但不会让 CPU 计算突破硬件限制。Future.get() 会等待结果;在请求链路中需要明确超时、取消和错误传播。数据库连接池等外部资源仍有容量上限。
2. 共享状态与可见性
多个线程同时修改 count++ 不安全,因为读取、加一和写回不是一个不可分割操作。可使用 synchronized、AtomicInteger 或通过消息传递避免共享;选择取决于要保护的状态范围。
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
private final AtomicInteger value = new AtomicInteger();
int increment() { return value.incrementAndGet(); }
}
volatile 保证特定读取和写入的可见性与顺序约束,不使 count++ 自动变成原子操作。多个相关字段需要保持一致时,仅对单字段使用原子类也可能不足。
3. 生产服务中的边界
- 为外部 I/O 设置超时,确保取消后能够释放资源。
- 不在锁内执行慢网络调用;避免锁顺序不一致引发死锁。
- 线程池或虚拟线程数不是吞吐量的唯一限制,数据库连接、下游限流和内存都可能先达到上限。
- 测试要覆盖并发冲突,但一次“跑过”不能证明无竞争;结合压力测试、日志和监控。
自测
volatile int count的count++是否线程安全?不是。- 虚拟线程适合什么负载?大量可阻塞的 I/O 任务;CPU 密集计算仍受核心数限制。
4. 原子操作、锁与复合规则
AtomicInteger.incrementAndGet() 可原子地加一,但“先检查余额、再扣款”是包含多个步骤的业务规则,不能靠单个原子计数器自动保证。多个字段需要同时一致时,应使用同一把锁、数据库事务或重新设计状态所有权。
class Inventory {
private int stock = 3;
synchronized boolean reserve() {
if (stock == 0) return false;
stock--;
return true;
}
}
这里 synchronized 保护整个检查与修改过程。真实库存通常在数据库中,需要通过事务、行锁或乐观版本控制处理多实例并发;Java 进程内的锁无法协调另一台服务器。
5. 取消、超时与虚拟线程
调用 Future.cancel(true) 会请求中断正在运行的任务,但被调用代码需正确响应中断。捕获 InterruptedException 后若不能向上抛,应恢复中断标记 Thread.currentThread().interrupt();吞掉中断会使取消失效。虚拟线程适合大量阻塞式 I/O,但仍要限制数据库连接数、下游请求数和队列长度;不要用无界任务提交掩盖容量问题。
练习:让 20 个任务同时抢 3 个库存,验证成功次数恰好为 3;再去掉 synchronized,观察测试为何可能偶尔通过、偶尔失败。