在计算机世界里,对于锁大家并不陌生,在现代所有的语言中几乎都提供了语言级别锁的实现,为什么我们的程序有时候会这么依赖锁呢?这个问题还是要从计算机的发展说起,随着计算机硬件的不断升级,多核cpu,多线程,多通道等技术把计算机的计算速度大幅度提升,原来同一时间只能执行一条cpu指令的时代已经过去。随着多条cpu指令可以并行执行的原因,原来不曾出现的资源竞争随着出现,在程序中的体现就是随处可见的多线程环境。比如要更新数据库的一个信息,如果没有并发控制,多个线程同时操作的话,就会出现互相覆盖的现象发生。
锁要解决的就是资源竞争的问题,也就是要把执行的指令顺序化。
在互联网背景下,电商行业是普遍都是多线程执行,并发量大。比如下单秒杀抢购商品活动,属于高并发情况,库存的保证就尤其重要了,不能出现超卖现象。程序员所要做的事情就是需要单线程执行获取库存,再减库存操作,保证数据原子性。
在多台服务器中,锁就是重点。
2、应用
我们先来介绍下单线程下的情况,假设有个商品A,库存500件,假设每次购买1件,单价1元,这样就只有是500次下单时成功的,其他都不能下单。
2.1 Store商品存储-Dictionary
数据库不存在的情况下,用Dictionary来代替
创建了Store后,主程序创建下单,单线程执行。
执行结果:
执行正常,4人不能下单。
改成多线程执行,直接出现超卖现象,其中用Task.Run 模拟多线程,其中Order(int i) i变量注意,不能直接填写i,异步中上下文不存在,因为主线程以及跑完了,再运行异步Task线程,这时变量已是最后一个被覆盖。
解决方案是重复赋值变量替换
结果:
500多的人都买了。
解决方案:
2.1.1 lock 加锁
lock很明显影响性能。
执行结果:
正常。
2.1.2 使用ConcurrentDictionary
ConcurrentDictionary 本身是线程安全的,源代码就是加了lock
执行结果符合。
2.2 用Redis分布式锁
redis是单线程运行,所以适合。原理也很简单,运用redis以下指令
1、SETNX
SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
2、expire
expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
3、delete
delete key:删除key
在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。
原理如下:
1、先获取redis锁,设置一个随机字符串
执行了805次,看下输出最后数量有没有小于0
结果显示0,正确,没超卖