0%

悲观锁和乐观锁

悲观锁和乐观锁

悲观锁

悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次拿数据是都会上锁,如数据库中的行锁、表锁、读锁、写锁等操作都会上锁,java中的synchronized就是悲观锁的思想

任何的数据操作都会有数据加锁、用户态内核态切换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作

如:synchronized、ReentrantLock等都是悲观锁

ReentrantLock虽然使用的是CAS,但是其CAS是对于锁的获取,所以一开始就上锁了。与乐观锁在拿数据时不上锁的理念是不一样的,不是所有用了CAS操作的都是乐观锁

乐观锁

乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候回判断一下在此期间别人有没有更新这条数据,可以使用版本号机制和CAS算法实现。乐观锁适用于读多写少的情况,可以提升吞吐量,如java的atomic包下的原子变量类就是使用了CAS来实现的乐观锁

乐观锁不需要线程挂起,所以也叫做非阻塞同步

如:Atomic类都是乐观锁

举例

使用版本号实现乐观锁

在数据库中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一

CAS实现乐观锁

CAS就是比较与交换(compare and swap),是一种无锁算法,在不使用锁的情况下实现多线程之前的变量同步,CAS算法涉及到三个操作数

  • 需要读写的内存值 V
  • 进行比较的值 A
  • 拟写入的新值 B

仅当V的值等于A时,CAS才会通过原子方式用新值B来更新V值,否则不会执行任何操作

CAS的原子性是由CPU硬件指令保证的,由Unsafe类执行这些操作

缺点

  • ABA问题

    乐观锁有一个常见的问题就是ABA问题

    如果一个变量V初次读取时为A值,并且在准备赋值的时候检查到它仍然为A值,但是中途可能被改成其他值,然后又改为了A值,那么CAS就会误认为它从来没有被修改过

    AtomicStampedReference就可以解决ABA问题,其给每个变量都配备了一个时间戳stamp

  • 循环时间长开销大

    自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销

  • 只能保证一个共享变量的原子操作

    CAS只对单个共享变量有效,当操作涉及到多个共享变量时CAS无效,可以使用AtomicReference来保证引用对象之间的原子性

欢迎关注我的其它发布渠道