简体   繁体   English

缓存的Java多线程锁定策略

[英]Java multithreading locking strategy for caches

I am designing a cache where I can keep security prices, which are time consuming to calculate. 我正在设计一个可以保留安全价格的缓存,计算价格很耗时。 Once calculated, I store them in a map: security as key and price as value. 计算后,我将它们存储在地图中:安全性作为键,价格作为价值。 The easy solution is to use ConcurrentHashMap here but I am trying do this with use of multithreaded programs to understand different locking strategies. 简单的解决方案是在此处使用ConcurrentHashMap ,但我正在尝试通过使用多线程程序来了解不同的锁定策略。

Here I am trying different ways to take a lock in case I need to update price of the security (it can be treated as any entity class) in the process cache. 在这里,我尝试使用不同的方法来获取锁,以防万一我需要更新进程缓存中的安全性价格(可以将其视为任何实体类)。

First way: Here I am trying to provide locking in my cache class so that client side locking is not needed. 第一种方式:在这里,我试图在我的缓存类中提供锁定,以便不需要客户端锁定。

Problem in first way: Even if I need to update price for one security, I am taking a lock over all securities because MyCache is singleton and all cases (putPrice and getPrice method calls) the same lock instance is used so all other threads which are trying to update other securities are also waiting for lock, though that can be done in parallel. 第一种方式的问题:即使我需要更新一个证券的价格, MyCache锁定所有证券,因为MyCache是单例,并且所有情况(putPrice和getPrice方法调用)都使用相同的锁实例,因此所有其他线程尝试更新其他证券也正在等待锁定,尽管可以同时进行。

Code for first way: 第一种方式的代码:

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();
        }
    }
}

Second way: Here I am trying to acquire lock over a security, for this I am using Security(Instance controlled class) object in MyCache constructor. 第二种方法:在这里,我试图获取对安全性的锁定,为此,我在MyCache构造函数中使用了Security(实例控制的类)对象。 MyCache is not singleton as it was in first case. MyCache不像第一种情况那样单身。 Client code needs to instantiate new object of MyCache, passing Security object. 客户端代码需要实例化MyCache的新对象,并传递Security对象。

Problem in Second way: Here probably I am increasing complexity, if a lock should be acquired over Security why not we implement locking related code in Security class, we can provide getPrice and updatePrice methods there and use critical sections to stop multiple threads coming in same time for same Security(Instance controlled class, only one object for one Security). 第二种方式的问题:在这里,我可能正在增加复杂性,如果应该在Security上获取锁,为什么不在Security类中实现与锁相关的代码,则可以在那里提供getPrice和updatePrice方法,并使用关键部分来阻止多个线程进入同一线程同一安全性的时间(实例控制类,一个安全性仅一个对象)。

Code for second way: 第二种方式的代码:

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);
        }
    }
}

I'm not sure what you're trying to do in the second example, you have (1) an unnecessary constructor and field and (2) an unnecessary map to security (without further information I'm afraid this is useless). 我不确定在第二个示例中您要做什么,您有(1)不必要的构造函数和字段,以及(2)不必要的安全性映射(没有更多信息,恐怕这没有用)。 Your first solution is the most "correct" out of the two, unless you want to modify secId (in which case you'd need additional synchronization in the Security class). 您的第一个解决方案是两者中最“正确”的解决方案,除非您要修改secId (在这种情况下,您需要在Security类中进行其他同步)。

The most ideal solution in my opinion would be to use ConcurrentHashMap in this case and scrap the locking. 我认为最理想的解决方案是在这种情况下使用ConcurrentHashMap并取消锁定。

You have encountered the problem with simple concurrent structures in that updating the structure requires you to lock the whole structure, no matter what the key is that is used. 您已经遇到了简单并发结构的问题,因为无论使用什么密钥,更新结构都需要锁定整个结构。 This obviously hurts concurrency. 这显然会损害并发性。 Your cache has to be accessible to all threads, so locking it will block all other threads trying to access the structure. 您的缓存必须对所有线程都可访问,因此对其进行锁定将阻止所有其他尝试访问该结构的线程。

The reason for this is that adding or deleting an entry can cause internal structures in the map to be modified that affect other keys. 原因是添加或删除条目可能会导致映射中的内部结构被修改,从而影响其他键。 So you have to lock the whole structure. 因此,您必须锁定整个结构。

You first solution will work, but is the same as wrapping the map in a SynchronizedMap (probably worse). 您的第一个解决方案将起作用,但与将地图包装在SynchronizedMap (可能更糟)。

Your second solution has a lot of errors, but generally it won't work as it does not lock the map. 您的第二个解决方案有很多错误,但通常不会起作用,因为它不会锁定地图。

There are two ways forward: 有两种前进的方式:

  1. If you know in advance what all the securities are you can pre-build the cache containing all of the keys that are known. 如果您事先知道所有证券是什么,则可以预先构建包含所有已知密钥的缓存。 I believe that you can then get and put and you won't need to synchronize as you will only be overwriting existing map entries. 我相信您可以取放,并且您不需要同步,因为您只会覆盖现有的地图条目。 If you really want to be sure, you could create a Price class as the value item in your map that contains the price which you can modify and then pre-load the cache with Price objects and then modify the price field in the Price object. 如果确实希望确定,则可以在地图中创建一个Price类作为包含可修改价格的值项,然后使用Price对象预加载缓存,然后在Price对象中修改price字段。 You will never have to do a get and put after the initial load. 初始加载之后,您将无需做任何操作。

  2. If you don't know all of the keys in advance or you don't want to preload the cache, use a ConcurrentHashMap . 如果您不事先知道所有键,或者不想预加载缓存,请使用ConcurrentHashMap ConcurrentHashMap does synchronize, but it creates multiple segments internally and has clever strategies for partitioning the set of keys such that it doesn't have to lock the whole map, only one segment. ConcurrentHashMap确实进行了同步,但是它在内部创建了多个段,并且具有巧妙的策略来划分一组键,从而不必锁定整个地图,只需锁定一个段。 This means that there is a good chance of avoiding contention in the cache. 这意味着有很大的机会避免缓存中的争用。 The manual entry states that reads generally do not block and a concurrency value can be specified to the constructor to control the expected number of concurrent threads. 手动输入指出读取通常不会阻塞,并且可以向构造函数指定并发值以控制预期的并发线程数。

ConcurrentHashMap 并发哈希图

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

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