乐观锁和悲观锁

  • 悲观锁: 总是假设最坏的情况,即认为共享资源每次被访问时都会出现问题
  • 乐观锁: 则是假设最好的情况,即认为共享资源每次被访问时不会出现问题

1. 悲观锁

  • 悲观锁总是假设最坏的情况,认为共享资源每次被访问时都会出现问题(如共享数据被修改),所以每次获取资源操作时都会上锁。也就是:共享资源每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其他线程
  • Java中的synchronized关键字和ReentrantLock类都是悲观锁的实现方式:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public void performSynchronisedTask() {
    synchronized(this) {
    // 需要同步的操作
    }
    }

    private Lock lock = new ReentrantLock();
    lock.lock();
    try {
    // 需要同步的操作
    } finally {
    lock.unlock();
    }
  • 高并发场景下,悲观锁会导致大量线程阻塞,影响系统性能,还会存在死锁问题

2. 乐观锁

  • 乐观锁则是假设最好的情况,认为共享资源每次被访问时不会出现问题,所以线程可以不停地执行,无需枷锁也无需等待,只是在提交修改时去验证对应的资源是否被其他线程修改了(具体方法可以使用版本号机制或CAS算法)
  • Java中java.util.concurrent.atomic包下的原子类(如AtomicIntegerAtomicReference等)就是乐观锁的实现方式,这些类通过CAS(Compare-And-Swap)操作来实现线程安全:
    1
    2
    3
    4
    // LongAdder 在高并发场景下会比 AtomicInteger 和 AtomicLong 的性能更好
    // 代价就是会消耗更多的内存空间(空间换时间)
    LongAdder sum = new LongAdder();
    sum.increment();
  • 高并发情况下,乐观锁不存在锁竞争造成的线程阻塞,也不会有死锁问题,性能上更好一些,但如果冲突频繁,可能会导致大量的重试操作,影响性能

  • 理论上来说,悲观锁多用于写比较多的情况,乐观锁适用于写比较少的情况