Skip to main content

锁的对比

David LiuAbout 3 min

锁的对比

乐观锁 vs 悲观锁

悲观锁:每次操作前都加锁

乐观锁:CAS 或版本号机制

一般来说,MySQL 中的行锁、表锁中,行锁会发生死锁,表锁不会

悲观锁通常多用于写比较多的情况下(多写场景),避免频繁失败和重试影响性能。

乐观锁通常多于写比较少的情况下(多读场景),避免频繁加锁影响性能,大大提升了系统的吞吐量。

Lock vs sychronized

语法层面

  • synchronized 是关键字,源码在 jvm 中,用 c++实现
  • Lock 是接口,源码由 jdk 提供,用 java 语言实现
  • 使用 synchronized 时,退出同步代码块锁会自动释放,而使用 Lock 时,需要手动调用 unlock 方法释放锁

synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API

synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。

ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。

功能层面

  • 二者均属于悲观锁,都具备基本的互斥、同步、锁重入功能

    互斥:就是只有一个拿着锁

    同步:

    • synchronize:wait,notify
    • Lock:利用条件变量提供的:await,signal

    锁重入:是否可以对已经加上锁的对象加上第二道、第三道锁,到时候解锁也需要解多道

  • Lock 提供了许多 synchronized 不具备的功能,例如:

    获取等待状态、公平锁、可打断、可超时、多条件变量

    公平锁:先来先得(吞吐量不如非公平的高),非公平锁:可以插队

    可打断、可超时:

  • Lock 有适合不同场景的实现,如 ReentrantLock,ReentrantReadWriteLock

ReentrantLock 比 synchronized 增加了一些高级功能

相比synchronizedReentrantLock增加了一些高级功能。主要来说主要有三点:

  • 等待可中断 : ReentrantLock提供了一种能够中断等待锁的线程的机制,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。

  • 可实现公平锁 : ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。

  • 可实现选择性通知(锁可以绑定多个条件): synchronized关键字与wait()notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。

    典型如:阻塞队列 ArrayBlockingQueue 的 take 和 put 用的 notEmpty 和 notFull Condition 队列

如果你想使用上述功能,那么选择 ReentrantLock 是一个不错的选择。

性能层面

  • 在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不错
  • 在竞争激烈时,Lock 的实现通常提供更好的性能

截屏2023-02-02 22.29.51