锁是什么
锁在开发中是避免不了需要接触的,在java中的锁主要是用于保障在多线程并发的情况下数据的一致性
锁可以理解为当多个人去上一个厕所,当里面有人的时候先抢到厕所的时候,里面的人将门栓锁上,后面的人想上厕所时候发现里面有人,就会在外面等候厕所里面的人出来,就如线程,当一个线程获得了锁去访问公共资源,这时其他的线程去访问这个公共资源的时候,就会发现线程已经上了锁,所以就会进入等待
不加锁会发生什么情况
当并发的线程不加锁的时候会发生什么事情,例如常见的超卖问题
库存类:有50的商品库存
@Data
public class Stock {
//商品库存数
private Integer stock=50;
}
构建一个方法
@Service
public class StockService {
private Stock stock=new Stock();
//买东西方法
public void payStock(){
if (stock.getStock()>0){
//买东西库存-1
stock.setStock(stock.getStock()-1);
System.out.println("剩余库存数"+stock.getStock());
}else {
System.out.println("本期抢购已售完");
}
}
}
调用方法
@GetMapping("stock/pay")
public String getPayStock(){
stockService.payStock();
return "hello!";
}
当我们使用JMeter模拟并发情况的时候发现,商品出现超抢购的数量比库存的数量多
这时,我们只要在方法加锁(synchronized)问题就会解决
//买东西方法
public synchronized void payStock(){
if (stock.getStock()>0){
stock.setStock(stock.getStock()-1);
System.out.println("剩余库存数"+stock.getStock());
}else {
System.out.println("本期抢购已售完");
}
}
但是这不是最好的解决方案,要不然为什么会有那么多锁,在不同的业务使用不同的锁,具体还是需要根据业务需求来选择
什么时候锁会失效
多例模式:在单例模式的情况下锁不会失效,但是在多例的情况下锁会失效可以在方法类上加上@Scope注解自行尝试:prototype:多例模式
@Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS )
事务:两个线程同时开启事务,a线程先获取到锁,a线程执行完减商品后释放锁的同时b线程立马获取到了锁,且查询到了商品的数量,a线程还未提交,这时两个线程获取到的数量是一样的,在数据库中set相同的值。可在方法类上添加@Transactional开启事务自行尝试。
集群部署:情况就如单例模式差不多,当项目部署在多个服务器的时候,并发情况下每个服务器会访问一个共享资源,这时,服务器里都有一个实例,这时加锁就会失效
解决:使用更新sql语句完成判断以及更新数据(写操作本身会加锁),这样会解决上面锁失效的问题,但是也会有新的问题:
1.锁的范围问题
2.同一个商品有多条库存记录,这样一个sql语句难以解决这个问题
3.无法记录库存的变化状态