跳到主要内容

五、常见锁策略 + CAS + synchronized原理

一、乐观锁 & 悲观锁

锁的实现者,预测接下来锁冲突的概率,来决定接下来该怎么做。于是分为两大“门派”:

乐观锁:乐观锁是一种乐观的思想,预测接下来冲突概率不大或认为多个线程之间不会发生冲突,因此在访问数据时不会加锁,而是通过在读取数据时记录一个版本号,更新数据时如果版本号不一致,则认为数据已经被其他线程修改过,需要重新尝试更新(借助版本号或时间戳识别出当前的数据访问是否冲突)。例如 Java 中的 AtomicInteger 类,其内部实现使用了乐观锁机制。

悲观锁:悲观锁则是一种悲观的思想,预测接下来冲突概率比较大或认为多个线程之间会发生冲突,因此在访问数据时会对其加锁,以防止其他线程同时访问。

Synchronized 初始使用乐观锁策略,当发现锁竞争比较频繁的时候,就会自动切换成悲观锁策略。

通常来说悲观锁一般要做的工作要更多一些,效率会更低一些。乐观锁做的工作会更少一点,效率更高一点。

二、重量级锁 & 轻量级锁

知识补充:锁的核心特性 “原子性”,这样的机制追根溯源是 CPU 这样的硬件设备提供的。CPU 提供了 “原子操作指令”,操作系统基于 CPU 的原子指令,实现了 mutex 互斥锁。JVM 基于操作系统提供的互斥锁,实现了 synchronized 和 ReentrantLock 等关键字和类。

file

重量级锁:加锁解锁,过程更低效。加锁机制重度依赖了 OS 提供了 mutex。其中涉及到大量的内核态用户态切换,很容易引发线程的调度。而这个操作,相对来说成本都比较高。

轻量级锁:加锁解锁,过程更高效。加锁机制尽可能不使用 mutex,而是尽量使用用户态代码完成。实在搞不定了,再使用 mutex。涉及到少量的内核态用户态切换,不太容易引发线程调度。

一般情况下:一个乐观锁很可能是一个轻量级锁(不绝对),一个悲观锁很可能是一个重量级锁(不绝对)

需要注意的是,用户态的时间成本是比较可控的,而内核态的时间成本不太可控

> 用户态下的程序只能访问用户空间的数据和代码,无法直接访问内核空间中的数据和资源,而内核态下的程序可以访问并操作所有的系统资源。