synchronized/
ReentrantLock控制同步,结合
volatile保障可见性,利用
java.util.concurrent包
在Java开发中,并发控制是构建高性能、高可靠性系统的核心技术之一,由于多线程环境下共享资源的访问可能导致数据竞争、状态不一致等问题,因此必须通过合理的机制协调线程执行顺序与资源分配,以下从底层原理到上层工具链,全面解析Java中控制并发的核心策略与实践方案。
线程基础与创建方式
Java通过Thread类或实现Runnable接口创建线程,后者更推荐因支持接口复用,示例代码如下:
// 方式1:继承Thread类
class MyThread extends Thread {
public void run() { System.out.println("子线程运行"); }
}
new MyThread().start(); // 启动新线程
// 方式2:实现Runnable接口(推荐)
class MyRunnable implements Runnable {
public void run() { System.out.println("任务执行"); }
}
new Thread(new MyRunnable()).start(); // 将任务提交给新线程
关键特性:每个线程拥有独立栈空间,但共享堆内存和方法区数据,这种设计虽提升了效率,但也引入了数据竞争风险。
同步机制详解
synchronized关键字
这是Java最基础的互斥锁机制,可用于修饰实例方法、静态方法或代码块:
| 应用场景 | 锁定对象 | 作用域 | 特点 |
|—————-|————————-|———————-|————————–|
| 实例方法 | 当前对象 (this) | 整个方法体 | 可重入,自动释放锁 |
| 静态方法 | 类对象 (MyClass.class)| 整个方法体 | 全局唯一锁 |
| 代码块 | 指定对象 (obj) | 包裹的代码段 | 精确控制临界区范围 |
示例:
// 同步代码块(推荐细粒度控制)
Object lock = new Object(); // 自定义锁对象
synchronized(lock) {
// 仅锁定必要代码段
count++; // 避免多个线程同时修改count
}
注意事项:synchronized依赖JVM内置的Monitor Enter/Exit指令,属于重量级锁,频繁竞争会导致性能下降。
ReentrantLock显式锁
相较于synchronized,ReentrantLock提供更灵活的控制能力:
Lock lock = new ReentrantLock();
lock.lock(); // 获取锁
try {
// 临界区代码
} finally {
lock.unlock(); // 确保释放锁
}
优势:
- 可中断:
lockInterruptibly()响应中断请求 - 超时尝试:
tryLock(long time, TimeUnit unit)避免永久阻塞 - 公平性策略:构造函数传入
true实现先到先得 - 条件变量集成:配合
Condition实现复杂唤醒逻辑
线程池管理(Executor框架)
直接创建大量线程会导致系统资源耗尽,推荐使用ExecutorService统一管理:
| 线程池类型 | 核心参数 | 适用场景 | 拒绝策略(默认) |
|———————|———————————–|————————|———————–|
| FixedThreadPool | 固定大小的工作线程 | 长期稳定负载 | AbortPolicy(抛异常) |
| CachedThreadPool | 按需创建/回收,最大65535 | 短期突发任务 | AbortPolicy |
| SingleThreadPool | 单工作线程 | 顺序处理串行任务 | AbortPolicy |
| ScheduledThreadPool| 定时/周期性任务 | 延迟执行或周期调度 | AbortPolicy |
| CustomThreadPool | 自定义核心/最大线程数、队列容量 | 需精细控制的通用场景 | 可自定义处理器 |
配置示例:
ExecutorService pool = new ThreadPoolExecutor(
4, // corePoolSize
8, // maximumPoolSize
60L, TimeUnit.SECONDS, // keepAliveTime
new ArrayBlockingQueue<>(100), // workQueue
new ThreadPoolExecutor.CallerRunsPolicy() // RejectedExecutionHandler
);
关键参数调优:
corePoolSize: 常驻线程数,立即创建maximumPoolSize: 最大扩容数,超过则触发拒绝策略keepAliveTime: 空闲线程存活时间,防止资源浪费workQueue: 缓冲任务队列,常用LinkedBlockingQueue或ArrayBlockingQueue
原子操作与CAS(Compare-And-Swap)
Java通过java.util.concurrent.atomic包提供无锁化解决方案:
| 类名 | 典型用途 | 底层实现原理 |
|——————–|——————————|———————–|
| AtomicInteger | 自增/减操作 | 基于CPU的CAS指令 |
| AtomicLong | 长整型原子操作 | |
| AtomicReference | 引用对象的原子更新 | |
| AtomicStampedReference | 带版本号的引用更新 | 解决ABA问题 |
示例:
AtomicInteger counter = new AtomicInteger(0); counter.getAndIncrement(); // 原子自增并返回新值 counter.compareAndSet(0, 1); // 如果当前值为0则设为1,否则不变
优势:相比synchronized,CAS无需阻塞,性能更高,适用于低冲突场景。
volatile关键字与可见性保障
volatile修饰符强制线程从主存读取最新值,而非本地缓存:
private volatile boolean flag = false; // 确保多线程间可见性
适用场景:
- 状态标志位(如中断信号)
- 双重检查锁定(Double-Checked Locking)模式
- 轻量级同步需求(不保证原子性!)
注意:volatile仅保证可见性和有序性,不保证复合操作的原子性,如需原子更新,仍需结合Atomic类。
并发集合类选型指南
| 集合类型 | 非线程安全原版 | 并发安全版本 | 特点 |
|---|---|---|---|
| List | ArrayList | CopyOnWriteArrayList |
写时复制,读无锁 |
| Set | HashSet | ConcurrentSkipListSet |
跳表实现,高并发读写 |
| Map | HashMap | ConcurrentHashMap |
分段锁+CAS,弱一致性 |
| Deque | ArrayDeque | ConcurrentLinkedDeque |
无界队列,头尾插入高效 |
| BlockingQueue | LinkedList | ArrayBlockingQueue |
生产-消费者模式专用 |
典型用法:
Map<String, String> map = new ConcurrentHashMap<>();
map.putIfAbsent("key", "value"); // 如果不存在则放入
异步编程模型(CompletableFuture)
Java 8引入的CompletableFuture简化了异步编程:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 异步任务逻辑
return "result";
});
future.thenAccept(result -> System.out.println(result)); // 结果回调
组合操作:
thenCombine(): 合并两个Future的结果allOf(): 等待多个Future全部完成anyOf(): 任一Future完成即触发
死锁预防与排查
死锁产生条件(四个必要条件):
- 互斥:资源一次仅能被一个线程占用
- 占有且等待:持有资源的同时请求新资源
- 不可剥夺:已分配的资源不能被强制释放
- 循环等待:形成环形等待链
预防措施:
- 固定加锁顺序:所有线程按相同顺序获取锁
- 超时机制:使用
tryLock(timeout)避免无限等待 - 资源分级:按优先级顺序申请资源
- 死锁检测:通过日志监控锁持有情况
性能优化建议
| 优化方向 | 具体措施 |
|---|---|
| 减少锁粒度 | 缩小同步代码块范围,避免持有锁期间执行耗时操作 |
| 替代重量级锁 | 优先使用ReentrantLock或StampedLock代替synchronized |
| 消除伪共享 | 确保不同线程操作不同缓存行(添加填充字段) |
| 善用局部变量 | 将不变数据声明为final或局部变量,减少同步开销 |
| 异步化非关键路径 | 将IO密集型任务拆解为异步流程,提升吞吐量 |
相关问答FAQs
Q1: synchronized和ReentrantLock有什么区别?如何选择?
A: 主要区别如下:
| 特性 | synchronized | ReentrantLock |
|———————|————————-|————————-|
| 灵活性 | 低(仅基本锁功能) | 高(可中断、超时、公平性)|
| 性能开销 | 较高(需进入/退出管程) | 较低(正常情况更快) |
| 异常处理 | 无法自动释放锁 | finally块确保释放 |
| 条件变量 | 需配合wait()/notify() | 内置Condition对象 |
选择建议:简单场景用synchronized;需要中断、超时或复杂条件唤醒时用ReentrantLock。
Q2: 为什么不应该过度使用synchronized?
A: synchronized属于悲观锁机制,会阻塞未获得锁的线程,过度使用会导致:
- 上下文切换频繁:线程不断在就绪态和阻塞态间切换,消耗CPU资源;
- 优先级反转:低优先级线程持有锁,高优先级线程被迫等待;
- 活跃度下降:系统吞吐量随线程数增加反而降低;
- 死锁风险:多锁交叉持有易引发死锁。
替代方案:优先考虑无锁算法(如CAS)、并发集合
