共计 2164 个字符,预计需要花费 6 分钟才能阅读完成。
作者:废物大师兄 \
起源:www.cnblogs.com/cjsblog/p/9135118.html
秒杀的场景有很多,比方:抢购、抢票、抢红包等等。总之,就是在极短时间内有大量的申请。
咱们都晓得,这种零碎设计的大方向就是限流,即通过层层过滤,最终只让绝对较少的申请进入到外围业务解决层。
这里不谈秒杀设计,不谈应用队列等使申请串行化,就谈下怎么用锁来保证数据正确,就是曾经到减库存那一步了,在这一步中如果保障不超卖。
用队列的话,能够是 Java 主动的队列,也能够用 Redis 的 LPUSH RPOP
重点是扣减库存
我了解,次要的形式是加锁。加锁有两个层面:一个是程序层面,另一个是数据库层面。
分布式锁
这种场景下应该很少有人用 Java 自带的锁(比方:synchronized、Lock)吧,因为它们只在同一个 JVM 内无效,如果你的利用部署了多台的话,应该用分布式锁。
对于 Redis 分布式锁,能够浏览这篇文章:Spring Boot Redis 实现分布式锁
其实,这里加分布式锁就是将多线程申请转成单线程申请,因为每次只有一个线程取得锁并执行,其余都被阻塞了。
这里有一点须要留神,就是当你利用了事务的话可能会存在问题,请看上面的代码
可能有人会这样写,第一眼看起来挺好的,没问题啊,但认真实践证明是由问题的。
咱们晓得,mysql 默认的事务隔离级别是 REPEATABLE-READ
对于事务隔离级别这块儿,可在公众号 Java 技术栈搜寻浏览。
在这种隔离级别下,同一个事务中屡次读取,返回的数据是一样的
同时,Spring 申明式事务默认的流传个性 REQUIRED
Spring 申明式事务是 Spring AOP 最好的例子,Spring 是通过 AOP 代理的形式来实现事务的,也就是说在调用 reduceStock() 办法的之前就曾经开启了事务。
那么,在并发状况下可能会存在这样的状况,假如线程 T1 和 T2 都执行到这里,于是它们都开启了事务 S1 和 S2,T1 先执行,T2 后执行,
因为 T2 执行的时候事务曾经创立了,依据隔离级别,这个时候事务 S2 读取不到 S1 已提交的数据,于是就会呈现 T1 和 T2 读取到的值是一样的,即 T2 读取的是 T1 更新前的库存数据。
对于这一点,大家能够本人写个代码测试一下,上面是一段参考:
鉴于这种状况呢,能够将库存放到 Redis 中,咱们间接读写 Redis,这样能够防止受数据库事务的影响,当然这也会带来新的问题,不再探讨。
数据库乐观锁
CAS(compare and swap)比拟并替换
在 Java 中,一个线程想批改某个变量的值,那么第一步是将变量的值从主内存中读取到本人工作内存中,而后批改,最初写回主内存。这个过程能够归结为:读取——批改——写入,在写回内存的时候可能以后内存中那个值曾经产生了变动,这个时候如果持续写则会笼罩他人的数据,只有当内存中的那个值和它批改之前读到的那个值一样,才能够写入。这个跟数据库是一样的。Java 中通过 Unsafe 中 compareAndSwapObject 这样的办法类实现的,它间接调用 CPU 指令。
数据库中也有 CAS,乐观锁就是一种 CAS
经典的乐观锁实现:
数据减少一个版本标识,个别是通过为数据库表减少一个数字类型的“version”字段来实现。当读取数据时,将 version 字段的值一起读出,数据每更新一次,对此 version 值加一。当咱们提交更新的时候,判断数据库表对应记录的以后版本信息与第一次取出来的 version 值进行比对,如果数据库表以后版本号与第一次取出来的 version 值相等,则予以更新,否则认为是过期数据。
更新的时候带上版本号,只有以后版本号与更新之前查问时的版本统一,才会更新
ABA 问题
这里顺便多提一句,CAS 中的 ABA 问题
假如,原先的值是 A,线程 - 1 读取到的值是 A,想把它改成 D,然而在此期间,有可能其余线程曾经屡次批改过这个值,只不过最初当线程 - 1 筹备将 A 改成 D 的时候,它发现恰好还是 A,认为没有人改过,其实这时候的 A 曾经不是原来的 A 了。
也就是说,只管批改之前做了比拟,当然,依然会呈现如下状况:
产生起因
ABA 问题导致的起因,是 CAS 过程中只简略进行了“值”的校验,有些状况下,“值”尽管雷同,却曾经不是原来的数据了。
优化方向
CAS 不能只比对“值”,还必须确保的是原来的数据,能力批改胜利。
常见实际
“版本号”的比对,一个数据一个版本,版本变动,即便值雷同,也不应该批改胜利。
不仅要关注值,还要关注是不是原来的对象
基于“值”的 CAS 乐观锁,可能导致 ABA 问题。CAS 乐观锁,必须保障批改时的“此数据”就是“彼数据”,应该由“值”比对,优化为“版本号”比对。
参考
https://www.sohu.com/a/150900…\
https://blog.csdn.net/zhjunju…\
https://blog.csdn.net/rexct39…
近期热文举荐:
1.600+ 道 Java 面试题及答案整顿 (2021 最新版)
2. 终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!
3. 阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!