最后总结
搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析
最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化
(integer) 1
127.0.0.1:6379> setnx lock-num 1 – 其他客户端获取不到锁
(integer) 0
死锁: 如果加了锁,但是没有释放,就会导致死锁,其他客户端一直获取不到锁。
使用expire为锁key添加时间限定,到时间不释放,放弃锁
由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大。具体时间需要业务测试后确认。
-
expire lock-key second
-
pexpire lock-key milliseconds
127.0.0.1:6379> set name 123
OK
127.0.0.1:6379> setnx lock-name 1 – 锁的名称key
(integer) 1
127.0.0.1:6379> expire lock-name 20 – 使用expire为锁key添加时间限定
(integer) 1
127.0.0.1:6379> get name
“123”
GETSET key value
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
redis> GETSET db mongodb # 没有旧值,返回 nil
(nil)
redis> GET db
“mongodb”
redis> GETSET db redis # 返回旧值 mongodb
“mongodb”
redis> GET db
“redis”
2.2 RedisLock
@Component
@Slf4j
public class RedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 加锁
* @param key:productId
* @param value 当前时间+超时时间
*/
public boolean lock(String key, String value) {
//setnx----对应方法 setIfAbsent(key, value),如果可以加锁返回true,不可以加锁返回false
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
//下面这段代码时为了解决可能出现的死锁情况
String currentValue = redisTemplate.opsForValue().get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间:重新设置锁的过期时间value,并返回上一个过期时间
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
//currentValue =2020-12-28,两个线程的value=2020-12-29,只会有一个线程拿到锁
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
}
//解锁
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) {
log.error(“【redis分布式锁】解锁异常, {}”, e);
}
}
}
2.3 将redis分布式锁应用于秒杀业务
@Override
public void orderProductMockDiffUser(String productId) {
//加锁
//锁的过期时间为当前时间+过期时长
long time = System.currentTimeMillis()+TIMEOUT;
if(!redisLock.lock(productId,String.valueOf(time))){
throw new SellException(101,“人太多,稍后再来”);
}
//1.查询该商品库存,为0则活动结束。
int stockNum = stock.get(productId);
if(stockNum == 0) {
throw new SellException(100,“活动结束”);
}else {
//2.下单(模拟不同用户openid不同)
orders.put(KeyUtil.genUniqueKey(),productId);
//3.减库存
stockNum =stockNum-1;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.更新库存
stock.put(productId,stockNum);
}
//解锁
redisLock.unlock(productId,String.valueOf(time));
}
2.4 分析RedisLock
重点分析加锁逻辑,有两个逻辑需要考虑:
public boolean lock(String key, String value) {
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
//下面的代码是为了解决可能出现的死锁的情况????
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//下面这个逻辑又怎么理解????
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
}
为了解决可能出现的死锁的情况????
//秒杀业务方法
@Override
public void orderProductMockDiffUser(String productId) {
//加锁
//锁的过期时间为当前时间+过期时长
long time = System.currentTimeMillis()+TIMEOUT;
if(!redisLock.lock(productId,String.valueOf(time))){
throw new SellException(101,“人太多,稍后再来”);
}
//1.查询该商品库存,为0则活动结束。
int stockNum = stock.get(productId);
if(stockNum == 0) {
throw new SellException(100,“活动结束”);
}else {
//2.下单
orders.put(KeyUtil.genUniqueKey(),productId);
//3.减库存
stockNum =stockNum-1;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.更新库存
stock.put(productId,stockNum);
}
//解锁
redisLock.unlock(productId,String.valueOf(time));
}
假如我们将中间那段逻辑去掉会出现声明情况???
public boolean lock(String key, String value) {
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
return false;
}
① 线程A执行秒杀的业务逻辑方法,并对这个方法加了锁,key=proeuctId,value=加锁时间+过期时长,然后开始执行下单----》减库存-----》更新库存等操作,如果在执行的过程中,这段代码发生了异常,那么线程A是不会释放锁的,导致其他线程都无法获取到锁导致死锁的产生,所以下面的逻辑是很有必要加的,即如果当前时间晚于锁的过期时间,那么就会向下走if()条件:
//下面的代码是为了解决可能出现的死锁的情况????
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//下面这个逻辑又怎么理解????
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
② 可是下面的if()条件怎么理解?
currentValue=2020-12-18
假如现在两个线程A和B同时执行lock()方法,也就是这两个线程的value是完全相同的,都为value=2020-12-19,而他们都执行 String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
,会有一个先执行一个后执行:
假如线程A先执行,返回的oldValue=2020-12-18
,同时设置value = 2020-12-19
,由于oldvalue=currentValue
返回true,即A线程加了锁;
此时B线程继续执行 ,返回的oldValue=2020-12-19,oldvalue!=currentValue
,返回false,加锁失败。
所以这段代码的逻辑是只会让一个线程加锁。推荐公众号Java精选,回复Java面试,获取Redis相关面试题资料。
public boolean lock(String key, String value) {
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//下面这个逻辑又怎么理解????
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
}
我的面试宝典:一线互联网大厂Java核心面试题库
以下是我个人的一些做法,希望可以给各位提供一些帮助:
整理了很长一段时间,拿来复习面试刷题非常合适,其中包括了Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等,且还会持续的更新…可star一下!
283页的Java进阶核心pdf文档
Java部分:Java基础,集合,并发,多线程,JVM,设计模式
数据结构算法:Java算法,数据结构
开源框架部分:Spring,MyBatis,MVC,netty,tomcat
分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等
微服务部分:SpringBoot,SpringCloud,Dubbo,Docker
还有源码相关的阅读学习
df文档**
Java部分:Java基础,集合,并发,多线程,JVM,设计模式
数据结构算法:Java算法,数据结构
开源框架部分:Spring,MyBatis,MVC,netty,tomcat
分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等
微服务部分:SpringBoot,SpringCloud,Dubbo,Docker
[外链图片转存中…(img-C0Mu9kfF-1715476125892)]
还有源码相关的阅读学习
[外链图片转存中…(img-cfZUzIkY-1715476125892)]