StockController 类
package com.atyoyo.distributedlock.controller; import com.atguigu.distributedlock.service.StockService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class StockController { @Autowired private StockService stockService; //减库存 @GetMapping(value = "stock/deduct") public String deduct(){ stockService.deduct(); return "hello stock deduct!!!"; } }
StockService 类 @Service public class StockService { @Autowired private StockMapper stockMapper; /** * redisTemplate */ //StringRedisTemplate 和 RedisTemplate 序列化器不同 @Autowired private StringRedisTemplate redisTemplate; @Autowired private DistributedLockClient distributedLockClient; public void deduct() { DistributedRedisLock redisLock = distributedLockClient.getRedisLock("lock"); redisLock.lock(); try { String stock = redisTemplate.opsForValue().get("stock").toString(); //2.判断库存是否充足 if (stock != null && stock.length() != 0) { Integer st = Integer.valueOf(stock); if (st > 0) { //扣减库存 redisTemplate.opsForValue().set("stock", String.valueOf(--st)); } } } finally { redisLock.unlock(); } }
}
DistributedLockClient 工厂类
// @Component public class DistributedLockClient { @Autowired private StringRedisTemplate redisTemplate; public DistributedRedisLock getRedisLock(String lockName){ return new DistributedRedisLock(redisTemplate,lockName); } }
DistributedRedisLock 类 package com.atyoyo.distributedlock.lock; import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import java.util.Arrays; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; public class DistributedRedisLock implements Lock { private StringRedisTemplate redisTemplate; // 线程局部变量,可以在线程内共享参数 private String lockName; private String uuid; private Integer expire = 30; private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>(); public DistributedRedisLock (StringRedisTemplate redisTemplate, String lockName) { this.redisTemplate = redisTemplate; this.lockName = lockName; this.uuid = THREAD_LOCAL.get(); if (StringUtils.isBlank(uuid)) { this.uuid = UUID.randomUUID().toString(); THREAD_LOCAL.set(uuid); } } @Override public void lock() { this.lock(expire); } public void lock(Integer expire){ this.expire = expire; String script ="if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 then redis.call('hincrby', KEYS[1], ARGV[1], 1) redis.call('expire', KEYS[1], ARGV[2]) return 1 else return 0 end\n" + "\t\t\n" + "\t\tif redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1\n" + "\t\tthen\n" + "\t\t\tredis.call('hincrby', KEYS[1], ARGV[1], 1)\n" + "\t\t\tredis.call('expire', KEYS[1], ARGV[2])\n" + "\t\t\treturn 1\n" + "\t\telse \n" + "\t\t\treturn 0\n" + "\t\tend" ; if (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, expire.toString())){ try { // 没有获取到锁,重试 Thread.sleep(60); lock(expire); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void lockInterruptibly() throws InterruptedException { } @Override public boolean tryLock() { return false; } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public void unlock(){ String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then return nil elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 then return redis.call('del', KEYS[1]) else return 0 end\n" + "\t\n" + "\t\tif redis.call('hexists', KEYS[1], ARGV[1]) == 0\n" + "\t\tthen\n" + "\t\t\treturn nil\n" + "\t\telseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0\n" + "\t\tthen \n" + "\t\t\treturn redis.call('del', KEYS[1])\n" + "\t\telse \n" + "\t\t\treturn 0\n" + "\t\tend"; // 如果返回值没有使用Boolean,Spring-data-redis 进行类型转换时将会把 null // 转为 false,这就会影响我们逻辑判断 // 所以返回类型只好使用 Long:null-解锁失败;0-重入次数减1;1-解锁成功。 Long result = this.redisTemplate.execute(new DefaultRedisScript<> (script, Long.class), Arrays.asList(lockName), uuid); // 3.6.4. 使用及测试 // 在业务代码中使用: // 测试: // 测试可重入性: // 如果未返回值,代表尝试解其他线程的锁 if (result == null) { throw new IllegalMonitorStateException("attempt to unlock lock, not locked by lockName: " + lockName + " with request: " + uuid); } else if (result == 1) { THREAD_LOCAL.remove(); } } @Override public Condition newCondition() { return null; } }