乐观锁和悲观锁了解么?如何实现乐观锁?
悲观锁:每次访问资源前都会上锁,访问完后解锁。也是互斥锁,synchronized和ReentrantLock就是悲观锁。
乐观锁:很乐观,认为被访问的资源不会出问题,不加锁。只是在修改的时候验证资源是否被其他人修改了。
乐观锁的实现?两种实现
- 版本号机制
一般在数据表中加version字段,每次修改数据version都会+1。
读取数据时获取version值,更新数据时如果version值没变才更新。
- CAS比较并交换
CAS,即Compare and Swap (比较并交换。CAS操作包括三个参数: 值的内存地址V、旧的预期数A、新的值B。CAS操作会比较内存地址V的值与预期数A是否相等,如果相等,则将内存地址V的值更新为新值B,否则不做任何操作或者 自旋重试。它是一个原子操作。
Java中的CAS,Unsafe类提供了compareAndSwap本地方法。
乐观锁解决什么问题?或者与悲观锁相比的优势?(美团)
- 乐观锁用于解决并发安全问题的一种方式。
- 无锁的思想,减少锁竞争。解决并发场景下的性能问题,悲观锁在读取数据的时候会锁定资源然后修改,乐观锁则是只有在修改数据的时候检查冲突,提高了并发性能。
CAS的使用场景?!!!!重要和上面的问题差不多
- 减少锁竞争,解决并发场景下的性能问题。
- 适合读多写少的场景,搭配volatile关键字,比如说几个线程写,很多个线程去读。
讲讲CAS?
- 首先讲概念
CAS,即Compare and Swap (比较并交换。CAS操作包括三个参数: 值的内存地址V、旧的预期数A、新的值B。CAS操作会比较内存地址V的值与预期数A是否相等,如果相等,则将内存地址V的值更新为新值B,否则不做任何操作或者 自旋重试。它是一个原子操作
- Java中的实现
Java中用unsafe类实现的,unsafe类都是native方法,Java方法不能操作内存,native方法可以操作内存。
- 硬件实现
通过cpu的原子指令cmpxchg,对总线加锁。保证CPU的独占。
CAS底层原理,谈谈对unsafe的理解?7. CAS
Unsafe类 :是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe类可以直接操作特定内存的数据。(Unsafe类可以对内存进行操作)
注意:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的所有方法都直接调用操作系统底层资源执行相应任务。
CAS底层原理 : 基于unsafe类的compareAndSwapxxx这个native方法,底层实现是一条CPU的原子指令。
也就是说CAS的原子性实际上是CPU实现独占的,比起用synchronized重量级锁,这里的排他时间要短很多,所以在多线程情况下性能会比较好。
为什么CAS在硬件层面是原子的?(大厂问得深、阿里)
cas是cpu的原子指令cmpxchg,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现独占的
AtomicInteger实现
类主要利用CAS+volatile和native方法来保证原子操作(volatile修饰原子类在保存的变量)
getAndIncrement() 底层采用 Unsafe CAS自旋的方式
自旋步骤:先获取该对象value变量地址内存的值 作为预期值,然后比较 内存地址的值 与 预期值是否相等,如果相同设置新的值,不相同重新自旋获取内存地址的值。
CAS有什么问题?
CAS的三个问题
- ABA问题。
- 竞争比较激烈的时候,并发比较大的时候。CAS会一直重试自旋,导致CPU开销大。
- 只能保证对一个变量的原子操作,后来AtomicReference可以将多个变量放在一个对象中保证原子操作。
CAS的ABA问题如何解决?
- 在每次修改时添加一个版本号或者时间戳,后续使用CAS更新不止要判断内存地址V中的值和预期原值是否相同,还要判断版本号或者时间戳是否变动过,如果二者都相同,那么说明这个值没有被修改过。
- Java中在Atomic包下也提供了
AtomicStampedReference来解决ABA问题。
CAS只能对一个变量进行原子操作如何解决?
Java中提供了AtomicReference这个类,将多个变量放在一个对象中解决。
AtomicLong使用cas作为原子累加计数器在高并发场景重试次数过多导致CPU控制?高并发计数累加有什么更好的方案吗?
使用jdk8中的LongAdder,采用了分段的思想,在高并发计数累加场景下性能远高于AtomicLong。
具体原理介绍可以看这篇文章cloud.tencent.com/developer/a…
LongAdder longAdder = new LongAdder();
longAdder.add(1000L);
longAdder.increment();