为了账号安全,请及时绑定邮箱和手机立即绑定

缓存的 Java 多线程锁定策略

缓存的 Java 多线程锁定策略

温温酱 2021-08-04 17:10:23
我正在设计一个缓存,我可以在其中保存安全价格,这些价格计算起来很耗时。计算完成后,我将它们存储在地图中:安全性作为关键,价格作为价值。简单的解决方案是在ConcurrentHashMap这里使用,但我正在尝试使用多线程程序来理解不同的锁定策略。在这里,我正在尝试不同的方法来获取锁定,以防我需要更新进程缓存中的证券价格(它可以被视为任何实体类)。第一种方式:在这里,我试图在我的缓存类中提供锁定,以便不需要客户端锁定。第一种方式的问题:即使我需要更新一种证券的价格,我MyCache也会锁定所有证券,因为是单例,并且所有情况(putPrice 和 getPrice 方法调用)都使用相同的锁实例,因此所有其他正在尝试的线程更新其他证券也在等待锁定,尽管这可以并行完成。第一种方式的代码:class Security {    int secId;}// Singleton class MyCachepublic class MyCache {    private static final HashMap<Security, BigDecimal> cache = new HashMap();    private final static ReadWriteLock lock = new ReentrantReadWriteLock();    public BigDecimal getPrice(Security security) {        lock.readLock().lock();        try {            BigDecimal price = cache.get(security);            if (price == null) {                price = new BigDecimal(-9999.9999);            }            return price;        } finally {            lock.readLock().unlock();        }    }    public void putPrice(Security security, BigDecimal price) {        lock.writeLock().lock();        try{            cache.put(security, price);        }finally {            lock.writeLock().unlock();        }    }}第二种方式:在这里,我试图获取对安全性的锁定,为此我在MyCache构造函数中使用了 Security(Instance Controlled class) 对象。MyCache不像第一种情况那样是单身。客户端代码需要实例化 MyCache 的新对象,传递 Security 对象。第二种方式的问题:这里可能我增加了复杂性,如果应该通过 Security 获取锁为什么我们不在 Security 类中实现与锁定相关的代码,我们可以在那里提供 getPrice 和 updatePrice 方法并使用关键部分来阻止多个线程进入相同相同安全的时间(实例控制类,一个安全只有一个对象)。第二种方式的代码:class Security {    private int secId;    private final static HashMap<Integer, Security> map = new HashMap<>();    private Security(Integer secId) {        this.secId = secId;    }    public static synchronized Security getSecurity(Integer secId) {        Security security = map.get(secId);        if (security == null) {            security = new Security(secId);            map.put(secId, security);        }        return security;    }}
查看完整描述

2 回答

?
湖上湖

TA贡献2003条经验 获得超2个赞

我不确定您在第二个示例中要做什么,您有 (1) 一个不必要的构造函数和字段,以及 (2) 一个不必要的安全映射(没有更多信息,我担心这是无用的)。您的第一个解决方案是两者中最“正确”的,除非您想修改secId(在这种情况下,您需要在Security类中进行额外的同步)。

我认为最理想的解决方案是ConcurrentHashMap在这种情况下使用并取消锁定。


查看完整回答
反对 回复 2021-08-04
?
蛊毒传说

TA贡献1895条经验 获得超3个赞

您遇到了简单并发结构的问题,即更新结构需要您锁定整个结构,无论使用的是什么密钥。这显然会损害并发性。您的缓存必须可供所有线程访问,因此锁定它会阻止所有其他尝试访问该结构的线程。

这样做的原因是添加或删除条目会导致映射中的内部结构被修改,从而影响其他键。所以你必须锁定整个结构。

您的第一个解决方案将起作用,但与将地图包装在 a 中SynchronizedMap(可能更糟)相同。

您的第二个解决方案有很多错误,但通常它不会起作用,因为它不会锁定地图。

有两种前进方式:

  1. 如果您事先知道所有证券是什么,您可以预先构建包含所有已知密钥的缓存。我相信您然后可以获取和放置并且您不需要同步,因为您只会覆盖现有的地图条目。如果您真的想确定,您可以在地图中创建一个 Price 类作为价值项目,其中包含您可以修改的价格,然后使用 Price 对象预加载缓存,然后修改 Price 对象中的价格字段。您将永远不必在初始加载后执行 get 和 put 操作。

  2. 如果您事先不知道所有键或者不想预加载缓存,请使用ConcurrentHashMapConcurrentHashMap确实同步,但它在内部创建了多个段,并有巧妙的策略来分割一组键,这样它就不必锁定整个地图,只需要锁定一个段。这意味着很有可能避免缓存中的争用。手动条目指出读取通常不会阻塞,并且可以向构造函数指定并发值以控制预期的并发线程数。


查看完整回答
反对 回复 2021-08-04
  • 2 回答
  • 0 关注
  • 174 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信