猿问

缓存的 Java 多线程锁定策略

我正在设计一个缓存,我可以在其中保存安全价格,这些价格计算起来很耗时。计算完成后,我将它们存储在地图中:安全性作为关键,价格作为价值。简单的解决方案是在ConcurrentHashMap这里使用,但我正在尝试使用多线程程序来理解不同的锁定策略。


在这里,我正在尝试不同的方法来获取锁定,以防我需要更新进程缓存中的证券价格(它可以被视为任何实体类)。


第一种方式:在这里,我试图在我的缓存类中提供锁定,以便不需要客户端锁定。


第一种方式的问题:即使我需要更新一种证券的价格,我MyCache也会锁定所有证券,因为是单例,并且所有情况(putPrice 和 getPrice 方法调用)都使用相同的锁实例,因此所有其他正在尝试的线程更新其他证券也在等待锁定,尽管这可以并行完成。


第一种方式的代码:


class Security {

    int secId;

}

// Singleton class MyCache

public 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;

    }

}


温温酱
浏览 175回答 2
2回答

湖上湖

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

蛊毒传说

您遇到了简单并发结构的问题,即更新结构需要您锁定整个结构,无论使用的是什么密钥。这显然会损害并发性。您的缓存必须可供所有线程访问,因此锁定它会阻止所有其他尝试访问该结构的线程。这样做的原因是添加或删除条目会导致映射中的内部结构被修改,从而影响其他键。所以你必须锁定整个结构。您的第一个解决方案将起作用,但与将地图包装在 a 中SynchronizedMap(可能更糟)相同。您的第二个解决方案有很多错误,但通常它不会起作用,因为它不会锁定地图。有两种前进方式:如果您事先知道所有证券是什么,您可以预先构建包含所有已知密钥的缓存。我相信您然后可以获取和放置并且您不需要同步,因为您只会覆盖现有的地图条目。如果您真的想确定,您可以在地图中创建一个 Price 类作为价值项目,其中包含您可以修改的价格,然后使用 Price 对象预加载缓存,然后修改 Price 对象中的价格字段。您将永远不必在初始加载后执行 get 和 put 操作。如果您事先不知道所有键或者不想预加载缓存,请使用ConcurrentHashMap.&nbsp;ConcurrentHashMap确实同步,但它在内部创建了多个段,并有巧妙的策略来分割一组键,这样它就不必锁定整个地图,只需要锁定一个段。这意味着很有可能避免缓存中的争用。手动条目指出读取通常不会阻塞,并且可以向构造函数指定并发值以控制预期的并发线程数。
随时随地看视频慕课网APP

相关分类

Java
我要回答