锁: 保证程序的原子性操作。
我们在单台服务器运行多线程的时候,会出现重买超卖的情况,这个时候可以通过加锁来保证代码的原子性操作,但是当我们多台服务器对应一个数据库的时候,依旧会出现线程安全问题,这个时候该怎么解决
加锁:可以使用 :synchronized或lock锁---本地JVM解决
redis解决--分布式锁
使用分布式锁
项目:
pom依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ykq</groupId> <artifactId>distrinct-lock</artifactId> <version>0.0.1-SNAPSHOT</version> <name>distrinct-lock</name> <description>分布式锁</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.13.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
配置类
server.port=8001 spring.datasource.username=root spring.datasource.password=123456789 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/distrinct-lock?serverTimezone=UTC mybatis.mapper-locations=classpath:/mapper/*.xml spring.redis.host=192.168.75.129 spring.redis.port=6379
mapper.xml文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ykq.distrinctlock.dao.ProductStockDao"> <select id="findStockByProductId" resultType="integer"> select num from tbl_stock where productId=#{productId} </select> <update id="updateStockByProductId"> update tbl_stock set num=num-1 where productId=#{productId} </update> </mapper>
controller层
@RestController @RequestMapping("productStock") public class ProductStockController { @Autowired private ProductStockService productStockService; //减库存 restful风格.再地址栏作为参数 @RequestMapping("decreaseStock/{productId}") public String decreaseStock(@PathVariable("productId") Integer productId){ return productStockService.decreaseStock(productId); } }
dao层
@Mapper public interface ProductStockDao { public Integer findStockByProductId(Integer id); public void updateStockByProductId(Integer id); }
service层
public interface ProductStockService { //减少库存 public String decreaseStock( Integer productId); }
实现代码 (主要)
@Service public class ProductStockServiceImpl2 implements ProductStockService { @Autowired private ProductStockDao productStockDao; //分布式锁 @Autowired private StringRedisTemplate redisTemplate; @Override public String decreaseStock(Integer productId) { ValueOperations<String, String> forValue = redisTemplate.opsForValue(); Boolean flag = forValue.setIfAbsent("product:" + productId, "ykq郑帅", 30, TimeUnit.SECONDS); if (flag) {//表示获取分布锁成功 try { //查看该商品的库存数量 Integer stock = productStockDao.findStockByProductId(productId); if (stock > 0) { //修改库存每次-1 productStockDao.updateStockByProductId(productId); System.out.println("扣减成功!剩余库存数:" + (stock - 1)); return "success"; } else { System.out.println("扣减失败!库存不足!"); return "fail"; } } finally { redisTemplate.delete("product:" + productId);//释放锁资源 } } else { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } return decreaseStock(productId); } } }
演示:
配置nginx
使用JMeter压测
缺陷: 当程序执行时间超过锁的时间。还是会发生线程安全问题
解决: 使用watch dog机制。----第三方框架完成redisson
使用watch dog机制 解决当程序执行时间超过锁的时间导致的线程安全问题
创建Redisson
@Configuration public class Redisson { @Bean public RedissonClient redissonClient(){ Config config = new Config(); config.useSingleServer() // use "rediss://" for SSL connection .setAddress("redis://192.168.75.129:6379"); RedissonClient redissonClient = org.redisson.Redisson.create(config); return redissonClient; } }
在service中使用
@Service public class ProductStockServiceImpl2 implements ProductStockService { @Autowired private ProductStockDao productStockDao; //看门狗机制--守护线程 @Autowired private Redisson redisson; @Override public String decreaseStock(Integer productId) { RedissonClient redissonClient = redisson.redissonClient(); RLock lock = redissonClient.getLock("product:" + productId); try { lock.lock(30, TimeUnit.SECONDS); //这里的锁时间必须是30s //查看该商品的库存数量 Integer stock = productStockDao.findStockByProductId(productId); if (stock > 0) { //修改库存每次-1 productStockDao.updateStockByProductId(productId); System.out.println("扣减成功!剩余库存数:" + (stock - 1)); return "success"; } else { System.out.println("扣减失败!库存不足!"); return "fail"; } } finally { lock.unlock();//释放锁 } }
使用JMeter压测