携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情 >>
什么是乐观锁
乐观锁:
认为在每次读取数据的时候都是安全的,别人不会修改,所以不会上锁,但是再更新的时候会判断一下别人有没有去更新这个数据,有这放弃修改,没有则执行.乐观锁一般会使用版本号机制,CAS算法实现
- 版本号机制
一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数,当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则将会重试更新操作,直到更新成功。 2. CAS算法 即 compare and swap (比较与交换) ,是一种有名的 无锁算法 。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。
CAS 算法
涉及到三个操作数:
① 需要读写的内存值 V
② 进行比较的值 A
③ 拟写入的新值 B
当且仅当 V 的值等于 A 时, CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即 不断的重试。
我们用java.util.concurrent.atomic包下的AtomicInteger说明
我们都知道i++不是原子性的,先是读取i的值,再加1,再复制给i分成了三个步骤,再多线程下是不安全的,我们来看AtomicInteger是如何保证线程安全的
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
#value在内存中地址的偏移量
private static final long valueOffset;
#静态代码块 给valueOffset赋值
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
#存储整数值 并用volatile修饰保证了可见性
private volatile int value;
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
#获取value的值
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
#返回value的原始值
return var5;
}
自增操作:获取value的值,CAS操作成功将value修改成var5+var4,并退出循环,返回value的原始值,失败一直自旋操作直到成功
- 优缺点
AtomicInteger的优点
1.乐观锁,性能较强,利用CPU自身的特性保证原子性,即CPU的指令集封装compare and swap两个操作为一个指令来保证原子性, volatile保证了可见性和防止指令重排序
2.适合读多写少模式
但是缺点明显
1.自旋,消耗CPU性能,所以写的操作较多推荐sync
2.仅适合简单的运算,否则会产生ABA问题,自旋的时候,别的线程可能更改value,然后又改回来,此时需要加版本号解决,JDK提供了AtomicStampedReference和AtomicMarkableReference解决ABA问题,提供基本数据类型和引用数据类型版本号支持