由于用户同时访问线上的下订单接口,导致在扣减库存时出现了异常,这是一个很典型的并发问题,本篇文章为解决并发问题而生,采用的技术为Redis锁机制+多线程的阻塞唤醒方法。
在实现Redis锁机制之前,我们需要了解一下前置知识。
一、前置知识
1、多线程
将wait()、notifyAll()归为到多线程的方法中略有一些不恰当,这两个方法是Object中的方法。
① 当调用了wait()方法后,让当前线程进入等待状态,并且让当前线程释放对象锁,等待既为阻塞状态,等待notifyAll()方法的唤醒。
wait()方法和sleep()方法有一些相似之处,都是使当前线程阻塞,但他们实际是有一些区别的。
执行wait() 方法之前需要请求锁,wait()方法执行的时候会释放锁,等待被唤醒的时候竞争锁。
sleep()只是让当前线程休眠一段时间,无视锁的存在。
wait() 是Object类的方法 sleep()是Thread的静态方法
② notifyAll()方法为唤醒wait()中的线程。
notifyAll() 和 notify() 方法都是可以唤醒调用了wait()方法,而陷入阻塞的线程。
但是notify()是随机唤醒这个阻塞队列中随机的一个线程,而notifyAll()是唤醒所用的调用了wait()方法而陷入阻塞的线程,让他们自己去抢占对象锁。
notifyAll() 和 notify() 也都是必须在加锁的同步代码块中被调用,它们起的是唤醒的作用,不是释放锁的作用,只用在当前同步代码块中的程序执行完,也就是对象锁自然释放了,notifyAll() 和 notify()方法才会起作用,去唤醒线程。
wait()方法一般是和notify() 或者 notifyAll() 方法一起连用的。
2、Redis
加锁的过程本质上就是往Redis中set值,当别的进程也来set值时候,发现里面已经有值了,就只能放弃获取稍后再试。
Redis提供了一个天然实现锁机制的方法。
在Redis客户端的命令为 setnx(set if not exists)
在集成Springboot中采用的方法为:
redisTemplate.opsForValue().setIfAbsent(key, value);
如果里面set值成功会返回True,如果里面已经存在值就会返回False。
在我们实际使用的时候,setIfAbsent()方法并不是总是返回True和False。
如果我们的业务中加了事务,该方法会返回null,不知道这是一个bug还是什么,这是Redis的一个巨坑&#