繁体   English   中英

缓存的Java多线程锁定策略

[英]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 (可能更糟)。

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

有两种前进的方式:

  1. 如果您事先知道所有证券是什么,则可以预先构建包含所有已知密钥的缓存。 我相信您可以取放,并且您不需要同步,因为您只会覆盖现有的地图条目。 如果确实希望确定,则可以在地图中创建一个Price类作为包含可修改价格的值项,然后使用Price对象预加载缓存,然后在Price对象中修改price字段。 初始加载之后,您将无需做任何操作。

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

并发哈希图

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM