JVM本地锁由ReentrantLock或synchronized实现
模拟场景
假设有个共享库存资源,多线程进行访问,每次访问库存-1.
@Data
public class StockDemo {
private Integer stock = 5000;
}
再controller -> service 进行访问调度
@Service
public class StockDemoService {
private StockDemo stockDemo = new StockDemo();
public void deduct(){
stockDemo.setStock(stockDemo.getStock()-1);
System.out.println("库存余量:" + stockDemo.getStock());
}
}
注意:此时的stockDemo是单例模式,所有请求进来访问的是一个stockDemo实例。
@RestController
public class StockDemoController {
@Autowired
private StockDemoService stockDemoService;
@GetMapping("/local/stock/deduct")
public String deduct(){
stockDemoService.deduct();
return "hello stock deduct";
}
}
端口号设置为10010.
启动完成后,通过页面访问
访问成功。
Jmeter并发测试
新建线程组,100个线程,每个线程发50个请求,请求间隔时间1s.
新建Http request,配置好发送路径
再配置好报表。
开始测试。
5000个请求执行完毕,吞吐量1520,没有错误。
那么如果正常来说,库存量此时应该是0。
查看控制台,发现最后库存并不是0,是604。
于是发生了超卖问题。即明明5000个商品已经卖完了,可是你显示还有库存,别的用户就还可以买。
synchronized
可以用synchronized解决,只要在service方法中加一个synchronized
@Service
public class StockDemoService {
private StockDemo stockDemo = new StockDemo();
public synchronized void deduct(){
stockDemo.setStock(stockDemo.getStock()-1);
System.out.println("库存余量:" + stockDemo.getStock());
}
}
重启后成功,再用jmeter测试。
吞吐量明显升高了,1653。
synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量
再看控制台
库存清空,问题解决。
ReentrantLock
用ReentrantLock实现,修改下service方法
@Service
public class StockDemoService {
private StockDemo stockDemo = new StockDemo();
private ReentrantLock lock = new ReentrantLock();
public void deduct(){
lock.lock();
try{
stockDemo.setStock(stockDemo.getStock()-1);
System.out.println("库存余量:" + stockDemo.getStock());
}finally {
lock.unlock();
}
}
}
注意:用的是try finally,为了保证最后肯定会调用lock.unlock()。
重启后再进行Jmeter测试
吞吐量明显降低,1035。
问题同样解决。