代码段同步/锁的合理使用:单例类Employee的方法是否需加锁或用原子变量
问题解答
1. Employee单例类的线程安全处理方案
结论:两个方法都需要做线程安全防护,使用synchronized修饰或者将salary改为原子类型两种方案都可行。
原因如下:
- 单例类的实例是多线程共享的,salary属于共享可变变量,如果不做防护会出现两类问题:
updateSalary操作本质是「读当前salary值→计算增量后的值→写回新值」的复合操作,不具备原子性,多线程并发调用时会出现更新丢失的问题。- 普通int类型的salary如果没有同步机制,
getSalary可能出现可见性问题,线程读到的是CPU缓存里的过期值,不是主内存中的最新值。
- 两种可行实现方案:
- 方案1:给两个方法加
synchronized修饰:锁对象为单例实例本身,保证同一时间只有一个线程执行这两个方法,同时满足原子性和可见性要求,实现简单,适合并发量不高的场景。 - 方案2:将salary定义为
AtomicInteger类型:get()方法天然保证可见性,addAndGet(increment)底层通过CAS实现原子更新,不需要额外加锁,高并发场景下性能比synchronized更优。
- 方案1:给两个方法加
2. 为什么不将synchronized设为方法默认标准
这个思路不成立,核心原因有三点:
- 不是所有多线程环境下的方法都需要同步:只有方法涉及访问共享可变变量时才需要做线程安全防护。如果方法是无状态的(仅使用方法内部局部变量、线程私有变量或者不可变变量),本身就不存在线程安全问题,加
synchronized完全是多余操作。 synchronized有不可忽视的性能开销:就算无锁竞争的场景下,JDK对synchronized做了偏向锁、轻量级锁的优化,依然有额外的性能损耗。如果所有方法默认加锁,绝大多数不需要同步的方法会平白损失性能,对高并发应用来说这个开销是完全无法接受的。- 同步策略需要结合业务灵活选择:线程安全的实现方案不止
synchronized一种,很多场景下用代码块级别的细粒度锁、读写锁、原子类、并发容器等方案更合适。默认给方法加synchronized等于强制使用最粗粒度、最重的锁方案,灵活性极差,完全不符合生产环境的性能优化要求。
举个实际场景的例子:Web服务的业务方法大多只有极少逻辑会涉及共享变量,大部分逻辑都是处理请求参数、调用下游服务、组装返回值。如果所有方法默认加synchronized,服务的并发能力直接降到串行水平,根本没办法支撑生产环境的流量要求。
内容的提问来源于stack exchange,提问作者Agam Goyal
相关产品推荐
相关产品推荐

