简体   繁体   中英

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.

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.

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 is not singleton as it was in first case. Client code needs to instantiate new object of MyCache, passing Security object.

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).

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). 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).

The most ideal solution in my opinion would be to use ConcurrentHashMap in this case and scrap the locking.

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).

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. 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 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. 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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