利用Redis实现分布式锁
在之前的答题对战项目中,游戏规则是根据双方回答同一道题所花时间长短判胜负,但测试过程中遇到一个偶现BUG:“对战开始后双方收到的题目不一致”,经过分析代码,发现问题原因在于发题逻辑没有加锁,所以写篇文章聊聊这个话题。
什么是锁
在编程领域,锁是一种用来做独占代码执行的方式,通俗来讲就是在打算做某件事情之前要申请许可证,如果没有得到许可证则无法做这件事。需要许可证的原因是因为并发现象:“同时出现多个人做这件事”,这种并发现象可能导致逻辑错误。在许可证这个比喻中,“人”是计算机领域的线程、进程等,要做的“事情”则是执行代码。
线程锁
假设有这样一段Java代码:
class Account {
private int money;
Account(int init) {
this.money = init;
}
// 购买一个价格为price的商品,成功返回true,否则返回false
public boolean buyWithPrice(int price) {
if (this.money < price) {
return false;
}
this.money -= price;
return true;
}
public static void main(String[] args) {
Account account = new Account(100);
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
if (account.buyWithPrice(100)) {
System.out.println("buy success");
}
}
}).start();
}
}
}
因为Javascript是单线程模型,不方便直接模拟线程并发,所以这一段示例代码用的是Java语言。示例中模拟了一个最简单的账户购物场景:“用账户余额(money)购买指定价格(price)的商品,购买成功返回true,余额不够则返回false”,场景中初始化一个余额为100的账户,然后创建两个线程去执行购买操作。
上述场景期望的正确结果是只有一个线程购买成功,另外一个会由于余额不足购买失败,所以我们执行代码只会打印一行'buy success'
。但实际运行结果却可能与我们所期望的不同,出现两个线程都购买成功,而出现两者都成功的原因是因为buyWithPrice
方法没有“加锁”(Java中称为非线程同步方法),在并发情况下,线程双方在this.money -= price
没有执行之前,都执行到if (this.money < price)