[英]Java multithreading locking strategy for caches
我正在设计一个可以保留安全价格的缓存,计算价格很耗时。 计算后,我将它们存储在地图中:安全性作为键,价格作为价值。 简单的解决方案是在此处使用ConcurrentHashMap
,但我正在尝试通过使用多线程程序来了解不同的锁定策略。
在这里,我尝试使用不同的方法来获取锁,以防万一我需要更新进程缓存中的安全性价格(可以将其视为任何实体类)。
第一种方式:在这里,我试图在我的缓存类中提供锁定,以便不需要客户端锁定。
第一种方式的问题:即使我需要更新一个证券的价格, MyCache
锁定所有证券,因为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(实例控制的类)对象。 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;
}
}
public class MyCache {
private static final HashMap<Security, BigDecimal> cache = new HashMap();
private final Security security;
public MyCache(Security security) {
this.security = security;
}
public BigDecimal getPrice(Security security) {
synchronized(security) {
BigDecimal price = cache.get(security);
if (price == null) {
price = new BigDecimal(-9999.9999);
}
return price;
}
}
public void putPrice(Security security, BigDecimal price) {
synchronized (security){
cache.put(security, price);
}
}
}
我不确定在第二个示例中您要做什么,您有(1)不必要的构造函数和字段,以及(2)不必要的安全性映射(没有更多信息,恐怕这没有用)。 您的第一个解决方案是两者中最“正确”的解决方案,除非您要修改secId
(在这种情况下,您需要在Security
类中进行其他同步)。
我认为最理想的解决方案是在这种情况下使用ConcurrentHashMap
并取消锁定。
您已经遇到了简单并发结构的问题,因为无论使用什么密钥,更新结构都需要锁定整个结构。 这显然会损害并发性。 您的缓存必须对所有线程都可访问,因此对其进行锁定将阻止所有其他尝试访问该结构的线程。
原因是添加或删除条目可能会导致映射中的内部结构被修改,从而影响其他键。 因此,您必须锁定整个结构。
您的第一个解决方案将起作用,但与将地图包装在SynchronizedMap
(可能更糟)。
您的第二个解决方案有很多错误,但通常不会起作用,因为它不会锁定地图。
有两种前进的方式:
如果您事先知道所有证券是什么,则可以预先构建包含所有已知密钥的缓存。 我相信您可以取放,并且您不需要同步,因为您只会覆盖现有的地图条目。 如果确实希望确定,则可以在地图中创建一个Price类作为包含可修改价格的值项,然后使用Price对象预加载缓存,然后在Price对象中修改price字段。 初始加载之后,您将无需做任何操作。
如果您不事先知道所有键,或者不想预加载缓存,请使用ConcurrentHashMap
。 ConcurrentHashMap
确实进行了同步,但是它在内部创建了多个段,并且具有巧妙的策略来划分一组键,从而不必锁定整个地图,只需锁定一个段。 这意味着有很大的机会避免缓存中的争用。 手动输入指出读取通常不会阻塞,并且可以向构造函数指定并发值以控制预期的并发线程数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.