侧边栏壁纸
博主头像
再见理想博主等级

只争朝夕,不负韶华

  • 累计撰写 112 篇文章
  • 累计创建 64 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

乐观锁与悲观锁问题

再见理想
2022-05-29 / 0 评论 / 0 点赞 / 292 阅读 / 906 字

一,悲观锁

总是认为线程不安全,不管什么情况都进行加锁,要是获取锁失败,就阻塞。实现:数据库加锁、Java中,synchronized加锁等。多用于写多读少,保证数据安全的场景。

二,乐观锁

总认为不会产生并发问题,因此不会加锁。但在更新时会判断其他线程在这之前有没有对数据进行修改。

实现1:使用版本号

一般是在数据表中加上version字段,表示数据被修改的次数,当数据被修改时,version加一。线程读取数据时也同时会读取version的值,当线程要更新数据时,若刚才读取到的version值与当前数据库中的version值相等时才更新

SQL:
update tb_goods set amount = amount - #{nums}, version = version + 1 where goods_id = #{goodsId} and version = #{version};

实现2:通过MySQL状态实现

例如秒杀中,判断数据库中商品库存-购买数是否大于或等于0。

SQL:
update tb_goods set amount = amount - #{nums} where goods_id = #{goodsId} and amount - #{nums} >= 0;

这两种乐观锁都是通过数据库实现,简单高效、稳定可靠。但缺点在于并发能力低,并发量阈值在300~700间。

实现3:缓存实现

compare and swap比较与交换。无锁算法,非阻塞同步。CAS算法涉及三个操作数(内存地址V、旧的预期值A、要更新的目标值B),当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。

一般情况下是一个自旋操作,即不断的重试。多用于读多写少,提高系统吞吐量的场景。

Redis实现乐观锁:

//开启事务支持
    redisTemplate.setEnableTransactionSupport(true);

    //监听key
    redisTemplate.watch("key");
    
    //开始事务
    redisTemplate.multi();

    //执行事务,如果其他线程对key中的value进行修改,则该事务将不会执行
    List<Object> list= redisTemplate.exec();

    if(list != null ){
        //操作成功
    }else{
        //操作失败
    }

三,CAS

AtomicXXX(AtomicInteger/AtomicBoolean等) 就是使用CAS原理。
CAS缺点:

  1. 如果CAS失败,会一直进行尝试,给CPU带来很大开销;
  2. 对多个共享变量操作时,循环CAS就无法保证操作的原子性;
  3. ABA问题:(ABA问题是什么?怎么解决?)

四,ABA问题

ABA问题是什么? 如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。

解决:
Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。

因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

0

评论区