记录一个Lock和sychronized应用及双检锁
synchronied
场景:数据存入redis,并读取缓存。app用户读取,量较大
逻辑
1.先读取缓存,判断缓存中是否有值,有返回,无,继续执行
2.对当前缓存类(CacheTemplateService)对象(this)加锁
3.再次从缓存中获取数据,再次判断,有数据返回,无,继续执行
为什么这里再次判断缓存中是否有锁,加入不判断,如果有大量请求等待持有锁(在synchronized处等待),当持有锁线程执行结束后(已写入缓存),就会有另外一个线程进入同步代码块,又会去查询数据库,写入缓存,其实是多余的,加了再次加缓存判断,即可避免。
4.执行查询数据库逻辑
5.写缓存
工具类
@Slf4j
@Component
public class CacheTemplateService {
@Autowired
private RedisManager redisManager;
public <T> T fetchCache(String key, Integer expire, TypeReference<T> clazz, CacheLoadable<T> cacheLoadable) {
String json = (String) redisManager.get(key);
if (Objects.nonNull(json) && StringUtils.isNotBlank(json) && !json.equalsIgnoreCase("null")
&& !"[]".equals(json) && !"{}".equals(json)) {
return JSON.parseObject(json, clazz);
}
synchronized (this) {
json = (String) redisManager.get(key);
if (Objects.nonNull(json) && StringUtils.isNotBlank(json) && !json.equalsIgnoreCase("null")
&& !"[]".equals(json) && !"{}".equals(json)) {
return JSON.parseObject(json, clazz);
}
// 核心业务
T result = cacheLoadable.load();
redisManager.put(key, expire, JSON.toJSONString(result));
return result;
}
}
public void invalidate(String key) {
redisManager.remove(key);
}
}
调用代码
List<ResLiveVO> resLiveVOS = cacheTemplateService
.fetchCache(RedisEnum.ADVANCE_LIVE_KEY.key, RedisEnum.ADVANCE_LIVE_KEY.expired,
new TypeReference<List<ResLiveVO>>() {
}, new CacheLoadable<List<ResLiveVO>>() {
@Override
public List<ResLiveVO> load() {
return selectAdvanceLive(uid);
}
});
Lock
场景:类似第一种场景,也是先从缓存查询数据,没有会调用第三方接口数据,app用户读取,量较大
逻辑
1.读缓存,无,继续
2.加lock锁
3.再次查询缓存 double check
4.调第三方接口,查询数据
5.写入缓存
@Override
public FundCompositeDTO fundCompositeData(String code) {
if (StringUtils.isBlank(code)) {
return null;
}
String key = RedisKeyHelper.compositeKey(code);
FundCompositeDTO cacheData = (FundCompositeDTO) redis.get(key);
if (cacheData != null) {
return cacheData;
}
try {
lock.tryLock(LOCK_TIMEOUT, TimeUnit.SECONDS);
cacheData = (FundCompositeDTO) redis.get(key);
if (cacheData != null) {
return cacheData;
}
FundCompositeDTO dto = fetchFundCompositeDataFromDB(code);
if (dto != null) {
int expire = (int) DateTimeUtils.getCurrent2EndDiffSenconds();
float lastScore = getsetLastTotalScore(code, dto.getTotalScore());
dto.setFlag(dto.getTotalScore() > lastScore);
redis.put(key, expire, dto);
}
return dto;
} catch (Exception e) {
log.info("Try Lock failed for the code#{}", code, e);
return fetchFundCompositeDataFromDB(code);
} finally {
lock.unlock();
}
}
比较二者
执行逻辑基本一样
Lock比较好吗,
lock获取锁有个时间限制(多久,与加锁内容执行时间有关),其他线程等待超时就放弃获取锁。
synchronized呢,经过锁升级过程,效率有所提升。
