0
点赞
收藏
分享

微信扫一扫

Java多线程锁与线程安全

在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. 关键区别总结

场景

数据一致性

性能影响

适用操作

无锁

❌ 可能丢失更新

⚡️ 高(无阻塞)

只读操作、线程隔离

synchronized

✅ 强一致性

⚠️ 中(上下文切换)

复合操作、共享写

🛡️ 二、多线程安全的实现策略

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. 其他线程安全技术

  • 线程安全容器
    ConcurrentHashMapCopyOnWriteArrayList,内置分段锁或写时复制,避免显式同步。
  • 线程局部存储(ThreadLocal
    变量在线程间隔离,适用于无状态组件(如Spring单例Bean)。
  • 不可变对象
    StringInteger,天然线程安全。

⚖️ 三、选型建议:原子类 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,并优先使用线程安全容器隔离并发复杂性。
举报

相关推荐

0 条评论