java中的锁,看完再也不怕面试(一)--------乐观锁

95 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情 >>

什么是乐观锁

乐观锁:

认为在每次读取数据的时候都是安全的,别人不会修改,所以不会上锁,但是再更新的时候会判断一下别人有没有去更新这个数据,有这放弃修改,没有则执行.乐观锁一般会使用版本号机制,CAS算法实现

  1. 版本号机制

一般是在数据表中加上一个数据版本号 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的原始值,失败一直自旋操作直到成功

  1. 优缺点

AtomicInteger的优点

1.乐观锁,性能较强,利用CPU自身的特性保证原子性,即CPU的指令集封装compare and swap两个操作为一个指令来保证原子性, volatile保证了可见性和防止指令重排序

2.适合读多写少模式

但是缺点明显

1.自旋,消耗CPU性能,所以写的操作较多推荐sync

2.仅适合简单的运算,否则会产生ABA问题,自旋的时候,别的线程可能更改value,然后又改回来,此时需要加版本号解决,JDK提供了AtomicStampedReference和AtomicMarkableReference解决ABA问题,提供基本数据类型和引用数据类型版本号支持