synchronized借助ConcurrentHashMap实

2021-07-05  本文已影响0人  小胖学编程

synchronized锁的不是代码块而是对象头。

历史文章

知识不是一个个的点,而是一个面。

维护缓存时,要对什么加锁?【synchronized能否对局部变量加锁,ConcurrentHashMap提供的computeIfAbsent源码分析】

JAVA并发(1)—java对象布局

image.png

SpringBoot2.x—SpringCache(6)缓存注意事项

image.png

2. 疑问?

带着疑问来思索,事倍功半。

加锁目的是实现排他性。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;
    }
}
上一篇 下一篇

猜你喜欢

热点阅读