synchronized借助ConcurrentHashMap实
2021-07-05 本文已影响0人
小胖学编程
synchronized锁的不是代码块而是对象头。
历史文章
知识不是一个个的点,而是一个面。
维护缓存时,要对什么加锁?【synchronized能否对局部变量加锁,ConcurrentHashMap提供的computeIfAbsent源码分析】
image.pngSpringBoot2.x—SpringCache(6)缓存注意事项
image.png2. 疑问?
带着疑问来思索,事倍功半。
加锁目的是实现排他性。sync加锁的原理是:锁的是对象头,而非代码块。我们在网上经常看到的业务代码是synchronized (this)
即锁当前对象。
但思考这个场景:
维护缓存的工具类,为了保证线程安全,一般只有一个线程去维护缓存,其他线程保持阻塞(防止缓存击穿)。
而缓存的key一般是局部变量,sync是无法对key加锁的。
而维护缓存的时候,加锁的粒度是每一个key级别的。即同一个key只有一个线程可以去维护缓存,不同的key互不影响。
3. 解决方案
在阅读seata源码时,发现
io.seata.core.rpc.netty.NettyClientChannelManager#releaseChannel
中有一个好的解决方案:
在此维护一个ConcurrentHashMap
的缓存,k为缓存的key(字符串),v为Object对象。
即完成了key和全局变量Object的绑定,sync锁的是key缓存的Object对象。
解决方案如图所示:
public interface CacheManager {
Map<String, Object> locks = new ConcurrentHashMap<>(8);
/**
* 在缓存中获取字符串信息
*
* @param supplier 回调的逻辑代码
* @param key 缓存的key
* @param time 失效时间,ms
* @return 缓存中存储的字符串信息
*/
default <T> String getInCache(Supplier<T> supplier, String key, long time) {
//获取锁
Object lock = locks.computeIfAbsent(key, k -> new Object());
//获取cache的key
T result;
//缓存中获取值
String v = get(key);
if (v == null) {
synchronized (lock) {
v = get(key);
if (v == null) {
result = supplier.get();
if (result != null) {
v = JSON.toJSONString(result);
put(key, v, Math.round(time * expireRate));
}
}
}
}
return v;
}
}