一.SETNX
1.实现原理
利用redis中的set命令来实现分布式锁。
从Redis 2.6.12版本开始,set可以使用下列参数:
SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
EX second :设置键的过期时间为second秒。 SET key value EX second效果等同于SETEX key second value 。
PX millisecond :设置键的过期时间为millisecond毫秒。 SET key value PX millisecond效果等同于PSETEX key millisecond value 。
NX :只在键不存在时,才对键进行设置操作。 SET key value NX效果等同于SETNX key value 。
XX :只在键已经存在时,才对键进行设置操作。‘
2.SETNX
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁,比如setnx sku HBL-89H-OL1。
当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
返回值:设置成功,返回 1 。设置失败,返回 0 。
3.代码实例
@Autowired
private RedisLock redisLock;
@ApiOperation(value = "redisLock")
@PostMapping("/pc/redisLock")
public ZTResponseMessage stockmanage(@RequestParam String sku){
ZTResponseMessage ztms = new ZTResponseMessage();
Long starttimes = System.currentTimeMillis();
try {
//加锁
long time = System.currentTimeMillis() + 1000 * 20; //超时时间:20秒,最好设为常量
boolean isLock = redisLock.lock(sku, String.valueOf(time));
if (!isLock) {
ztms.setCode(102);
ztms.setDesc("SKU:" + sku + "还有其他线程在进行操作!");
ztms.setSuccess(false);
return ztms;
}
Thread.sleep(10000);
redisLock.unlock(sku,String.valueOf(time));
ztms.setCode(102);
ztms.setDesc("SKU:" + sku + "操作成功!");
ztms.setSuccess(false);
return ztms;
} catch (Exception e) {
ztms.setCode(102);
ztms.setDesc("Exception异常提示:" + e.getMessage());
ztms.setSuccess(false);
}
Long endtimes = System.currentTimeMillis();
ztms.setSecond(endtimes - starttimes);
return ztms;
}
RedisLock 代码实现
@Component
public class RedisLock {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 加锁
* @param key 商品id
* @param value 当前时间+超时时间
* @return
*/
public boolean lock(String key, String value) {
if (redisTemplate.opsForValue().setIfAbsent(key, value)) { //这个其实就是setnx命令,只不过在java这边稍有变化,返回的是boolea
return true;
}
//避免死锁,且只让一个线程拿到锁
String currentValue = redisTemplate.opsForValue().get(key);
//如果锁过期了
if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间
String oldValues = redisTemplate.opsForValue().getAndSet(key, value);
/*
只会让一个线程拿到锁
如果旧的value和currentValue相等,只会有一个线程达成条件,因为第二个线程拿到的oldValue已经和currentValue不一样了
*/
if (!StringUtils.isEmpty(oldValues) && oldValues.equals(currentValue)) {
return true;
}
}
return false;
}
/**
* 解锁
* @param key
* @param value
*/
public void unlock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
logger.error("『redis分布式锁』解锁异常,{}", e);
}
}
}
二.Redisson实现分布式锁
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@ApiOperation(value = "redisLock")
@PostMapping("/pc/redisLock")
public String redisLock(){
String lockKey="lockKey";
RLock redisId=redisson.getLock(lockKey);//拿到锁对象
try { //问题一
redisId.lock();//j加锁
int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");
if(stock>0){
int realStock=stock-1;
stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value)
}else{
System.out.println("扣减失败,库存不足");
}
}finally {
redisId.unlock();
}
return "end";
}