01_环境准备
背景介绍:
在应用开发中,特别是web工程开发,通常都是并发编程,不是多进程就是多线程。这种场景下极易出现线程并发性安全问题,此时不得不使用锁来解决问题。在多线程高并发场景下,为了保证资源的线程安全问题,jdk为我们提供了synchronized关键字和ReentrantLock可重入锁,但是它们只能保证一个工程内的线程安全。在分布式集群、微服务、云原生横行的当下,如何保证不同进程、不同服务、不同机器的线程安全问题,jdk并没有给我们提供既有的解决方案。此时,我们就必须借助于相关技术手动实现了。目前主流的实现有以下方式:
-
基于mysql关系型实现
-
基于redis非关系型数据实现
-
基于zookeeper/etcd实现
所以重点来关注这三种分布式锁的实现,并深入源码剖析第三方分布式锁框架。
环境搭建
需要搭建一个对共享资源访问的环境。
使用库存作为共享资源,并设置初始值为5000:
@Data public class Stock { private Integer stock = 5000; }
controller层实现:
@GetMapping("/stock/deduct") public String deduct() { this.stockService.deduct(); return "hello stock deduct!!"; }
service层实现:
public void deduct() { stock.setStock(stock.getStock() - 1); System.out.println("库存余量:" + stock.getStock()); }
使用压力测试工具模拟并发情况下的库存扣减情况,设置100个线程,循环50次:
通过并发情况的扣减情况来看库存的扣减情况出现了问题:
压力测试工具测试报告的各参数说明:
-
Label 取样器别名,如果勾选Include group name ,则会添加线程组的名称作为前缀
-
# Samples 取样器运行次数
-
Average 请求(事务)的平均响应时间
-
Median 中位数
-
90% Line 90%用户响应时间
-
95% Line 90%用户响应时间
-
99% Line 90%用户响应时间
-
Min 最小响应时间
-
Max 最大响应时间
-
Error 错误率
-
Throughput 吞吐率
-
Received KB/sec 每秒收到的千字节
-
Sent KB/sec 每秒收到的千字节
测试结果:请求总数5000次,平均请求时间37ms,中位数(50%)请求是在36ms内完成的,错误率0%,每秒钟平均吞吐量2200次。
可以看到库存的扣减在并发情况下出现了问题,出现了重复扣减相同库存的情况,这是由于并发情况下在获取库存时不同的线程获取到相同的库存并进行扣减所以出现了该问题:
解决方式:
1、使用jdk提供的锁机制synchronized关键字:
public synchronized void deduct() { stock.setStock(stock.getStock() - 1); System.out.println("库存余量:" + stock.getStock()); }
库存扣减情况正常:
每秒钟平均吞吐量下降为1313次:
synchronized原理
ReentrantLock可重入锁同样可以解决并发问题:
库存扣减情况正常:
测试报告结果,每秒钟平均吞吐量下降为872次: