史上最全 Java 中各种锁的介绍
更多精彩原创内容请关注:JavaInterview,欢迎 star,支持鼓励以下作者,万分感谢。
锁的分类介绍
乐观锁与悲观锁
锁的一种宏观分类是乐观锁与悲观锁。乐观锁与悲观锁并不是特定的指哪个锁(Java 中也没有那个具体锁的实现名就叫
乐观锁或悲观锁),而是在并发情况下两种不同的策略。
乐观锁(Optimistic Lock)就是很乐观,每次去拿数据的时候都认为别人不会修改。所以不会上锁。但是如果想要更新数据,
则会在更新之前检查在读取至更新这段时间别人有没有修改过这个数据。如果修改过,则重新读取,再次尝试更新,循环上述
步骤直到更新成功(当然也允许更新失败的线程放弃更新操作)。
悲观锁(Pessimistic Lock)就是很悲观,每次去拿数据的时候都认为别人会修改。所以每次都在拿数据的时候上锁。
这样别人拿数据的时候就会被挡住,直到悲观锁释放,想获取数据的线程再去获取锁,然后再获取数据。
悲观锁阻塞事务,乐观锁回滚重试,它们个有优缺点,没有好坏之分,只有适应场景的不同区别。比如:乐观锁适合用于写
比较少的情况下,即冲突真的很少发生的场景,这样可以省去锁的开销,加大了系统的整个吞吐量。但是如果经常产生冲突,上层
应用会不断的进行重试,这样反而降低了性能,所以这种场景悲观锁比较合适。
总结:乐观锁适合写比较少,冲突很少发生的场景;而写多,冲突多的场景适合使用悲观锁。
乐观锁的基础 --- CAS
在乐观锁的实现中,我们必须要了解的一个概念:CAS。
什么是 CAS 呢? Compare-and-Swap,即比较并替换,或者比较并设置。
-
比较:读取到一个值 A,在将其更新为 B 之前,检查原值是否为 A(未被其它线程修改过,这里忽略 ABA 问题)。
-
替换:如果是,更新 A 为 B,结束。如果不是,则不会更新。
上面两个步骤都是原子操作,可以理解为瞬间完成,在 CPU 看来就是一步操作。
有了 CAS,就可以实现一个乐观锁:
public class OptimisticLockSample{ public void test(){ int data = 123; // 共享数据 // 更新数据的线程会进行如下操作 for (;;) { int oldData = data; int newData = doSomething(oldData); // 下面是模拟 CAS 更新操作,尝试更新 data 的值 if (data == oldData) { // compare data = newData; // swap break; // finish } else { // 什么都不做,循环重试 } } } /** * * 很明显,test() 里面的代码根本不是原子性的,只是展示了下 CAS 的流程。 * 因为真正的 CAS 利用了 CPU 指令。 * * */ }
在 Java 中也是通过 native 方法实现的 CAS。
public final class Unsafe { ... public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native