在Java多线程编程中,是否使用synchronized
锁、如何选择原子类或显式锁,直接决定了线程安全性和性能。下面从核心区别、线程安全实现策略及选型依据三方面详细解析。
🔒 一、synchronized
锁与无锁的区别
1. 无synchronized
的情况
- 数据竞争与不一致性
多个线程并发读写共享变量时,非原子操作(如i++
)会被拆分为“读-改-写”三步,中间可能被其他线程打断,导致最终结果不符合预期。例如,两个线程同时执行count++
(实际是count = count + 1
),若初始count=0
,可能两次操作后结果仍为1
。 - 可见性问题
线程修改变量后可能仅更新本地缓存,未同步到主内存,其他线程读取到旧值(尤其在未用volatile
时)。
2. 使用synchronized
的情况
- 互斥访问
锁住对象或类(静态方法)的监视器(Monitor),同一时间仅一个线程能执行同步代码块或方法,确保操作的原子性。 - 内存可见性
锁释放前会将修改强制刷到主内存,锁获取时会从主内存重新加载变量,解决可见性问题。
public class Counter {
private int count;
public synchronized void increment() { count++; } // 线程安全
}
3. 关键区别总结
场景 | 数据一致性 | 性能影响 | 适用操作 |
无锁 | ❌ 可能丢失更新 | ⚡️ 高(无阻塞) | 只读操作、线程隔离 |
有 | ✅ 强一致性 | ⚠️ 中(上下文切换) | 复合操作、共享写 |
🛡️ 二、多线程安全的实现策略
1. 锁机制
synchronized
优点:语法简单,自动释放锁,无泄漏风险。
缺点:不支持超时、公平锁或条件变量;阻塞不可中断。ReentrantLock
优点:支持公平锁、可中断锁、超时尝试(tryLock
)、多条件变量(Condition
)。
缺点:需手动释放锁(finally
中调用unlock()
),否则死锁风险高。
private Lock lock = new ReentrantLock();
public void safeUpdate() {
lock.lock();
try { /* 临界区 */ }
finally { lock.unlock(); }
}
2. 原子类(java.util.concurrent.atomic
)
- 原理
基于CAS(Compare-And-Swap)指令实现无锁更新,如AtomicInteger.incrementAndGet()
内部调用Unsafe.compareAndSwapInt()
。 - 优势
⚡️ 无阻塞算法,高并发下性能优于锁(如计数器场景)。 - 局限性
仅适用于单一变量原子操作,无法覆盖复合操作(如“先读后写”需配合自旋或版本控制)。
private AtomicInteger atomicCount = new AtomicInteger(0);
public void add() { atomicCount.incrementAndGet(); } // 线程安全且高效
3. 其他线程安全技术
- 线程安全容器
如ConcurrentHashMap
、CopyOnWriteArrayList
,内置分段锁或写时复制,避免显式同步。 - 线程局部存储(
ThreadLocal
)
变量在线程间隔离,适用于无状态组件(如Spring单例Bean)。 - 不可变对象
如String
、Integer
,天然线程安全。
⚖️ 三、选型建议:原子类 vs. 加锁
1. 优先使用原子类的场景
- 单一变量操作:计数器(
AtomicInteger
)、标志位(AtomicBoolean
)。 - 高并发低竞争:CAS自旋开销小于线程阻塞唤醒。
- 版本控制需求:使用
AtomicStampedReference
解决ABA问题(如无锁队列)。
2. 优先使用锁的场景
- 复合操作:需多个变量或操作作为整体执行(如转账:扣减A账户并增加B账户)。
- 高级同步需求:
- 公平性:
ReentrantLock(true)
保障先到先得。 - 超时与中断:
lock.tryLock(2, TimeUnit.SECONDS)
。 - 条件等待:
Condition.await()
替代Object.wait()
,支持多条件队列(如生产者-消费者模型)。
3. 决策流程图
graph TD
A[需线程安全的操作] --> B{操作类型}
B -->|单一变量原子更新| C[使用原子类]
B -->|复合操作/多变量| D{是否需要高级功能?}
D -->|是:超时、公平锁、条件变量| E[使用ReentrantLock]
D -->|否| F[使用synchronized]
💎 总结
- 无锁 vs. 加锁:无锁存在数据竞争;
synchronized
通过互斥和内存屏障保障安全,但有性能损耗。 - 原子类:首选于简单原子操作,性能高且无阻塞。
- 显式锁:适用于复杂同步逻辑,但需手动管理避免死锁。
- 综合策略:多数场景下,计数器用
AtomicInteger
,复合操作用ReentrantLock
,简单同步用synchronized
,并优先使用线程安全容器隔离并发复杂性。