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)、并发集合
原创文章,发布者:酷盾叔,转转请注明出处:https://www.kd.cn/ask/95771.html