乐观锁,顾名思义,就是说在操做共享资源时,它老是抱着乐观的态度进行,它认为本身能够成功地完成操做。但实际上,当多个线程同时操做一个共享资源时,只有一个线程会成功,那么失败的线程呢?乐观锁不会像悲观锁同样在操做系统中挂起,而仅仅是返回,而且系统容许失败的线程重试,也容许自动放弃退出操做。java
因此,乐观锁相比悲观锁来讲,不会带来死锁、饥饿等活性故障问题,线程间的相互影响也远远比悲观锁要小。更为重要的是,乐观锁没有因竞争形成的系统开销,因此在性能上也是更胜一筹。算法
CAS 是实现乐观锁的核心算法,它包含了 3 个参数:V(须要更新的变量)、E(预期值)和 N(最新值)。安全
只有当须要更新的变量等于预期值时,须要更新的变量才会被设置为最新值,若是更新值和预期值不一样,则说明已经有其它线程更新了须要更新的变量,此时当前线程不作操做,返回 V 的真实值。并发
在 JDK 中的 concurrent 包中,atomic 路径下的类都是基于 CAS 实现的。AtomicInteger 就是基于 CAS 实现的一个线程安全的整型类。下面咱们经过源码来了解下如何使用 CAS 实现原子操做。ide
咱们能够看到 AtomicInteger 的自增方法 getAndIncrement 是用了 Unsafe 的 getAndAddInt 方法,显然 AtomicInteger 依赖于本地方法 Unsafe 类,Unsafe 类中的操做方法会调用 CPU 底层指令实现原子操做。
高并发
//基于CAS操做更新值 public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } //基于CAS操做增1 public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } //基于CAS操做减1 public final int getAndDecrement() { return unsafe.getAndAddInt(this, valueOffset, -1); }
虽然乐观锁在并发性能上要比悲观锁优越,可是在写大于读的操做场景下,CAS 失败的可能性会增大,若是不放弃这次 CAS 操做,就须要循环作 CAS 重试,这无疑会长时间地占用 CPU。性能
在 Java7 中,经过如下代码咱们能够看到:AtomicInteger 的 getAndSet 方法中使用了 for 循环不断重试 CAS 操做,若是长时间不成功,就会给 CPU 带来很是大的执行开销。到了 Java8,for 循环虽然被去掉了,但咱们反编译 Unsafe 类时就能够发现该循环实际上是被封装在了 Unsafe 类中,CPU 的执行开销依然存在。测试
public final int getAndSet(int newValue) { while (true) { int current = get(); if (compareAndSet(current, newValue)) return current; } }
在 JDK1.8 中,Java 提供了一个新的原子类 LongAdder。LongAdder 在高并发场景下会比 AtomicInteger 和 AtomicLong 的性能更好,代价就是会消耗更多的内存空间。优化
咱们分别在“读多写少”、“读少写多”、“读写差很少”这三种场景下进行测试。this
对三种模式下的五个锁 Synchronized、ReentrantLock、ReentrantReadWriteLock、StampedLock 以及乐观锁 LongAdder 进行压测。压测结果以下图:
经过以上结果,咱们能够发现:
在写大于读的场景下,乐观锁的性能是最好的,其它 4 种锁的性能则相差很少;
在读和写差很少的场景下,两种读写锁以及乐观锁的性能要优于 Synchronized 和 ReentrantLock。在读大于写的场景下,读写锁 ReentrantReadWriteLock、StampedLock 以及乐观锁的读写性能是最好的。