2

Normally I would lock on a critical section like the following.

public class Cache {
    private Object lockObject = new Object();

    public Object getFromCache(String key) {
        synchronized(lockObject) {

            if (cache.containsKey(key)) {
                // key found in cache - return cache value
            }
            else {
                // retrieve cache value from source, cache it and return
            }
        }
    }
}

The idea being I avoid a race condition which could result in the data source being hit multiple times and the key being added to the cache multiple times.

Right now if two threads come in at about the same time for different cache keys, I will still block one.

Assuming the keys are unique - will the lock still work by locking on the key?

I think it won't work because I understand that the object reference should be the same for the lock to come into effect. I guess this comes down to how it checks for equality.

public class Cache {

    public Object getFromCache(String key) {
        synchronized(key) {

            if (cache.containsKey(key)) {
                // key found in cache - return cache value
            }
            else {
                // retrieve cache value from source, cache it and return
            }
        }
    }
}
Avner
  • 4,286
  • 2
  • 35
  • 42
  • No. The resource you need to protect is the cache, not the key. What you suggest doesn't make sense. – user207421 Jun 22 '17 at 02:56
  • 1
    Typically the key would hash into an array of lock object to avoid the instance issue. This is known as striping, where the stripe (resource) is protected from any mutations by this lock. In your example of a cache this is called memoization to avoid a [cache stampede](https://en.wikipedia.org/wiki/Cache_stampede), done by locking the internal hash table entry. To avoid locking on a read this would use an optimistic get and fallback (double-checked locking). [Caffeine](https://github.com/ben-manes/caffeine) and other caches implement this technique. – Ben Manes Jun 22 '17 at 04:06
  • Thanks Ben your explanation was useful. I will introduce an array of locks hashed on the key. – Avner Jun 22 '17 at 04:59

2 Answers2

0
public class Cache {

    private static final Set<String> lockedKeys = new HashSet<>();

    private void lock(String key) throws InterruptedException {
        synchronized (lockedKeys) {
            while (!lockedKeys.add(key)) {
                lockedKeys.wait();
            }
        }
    }

    private void unlock(String key) {
        synchronized (lockedKeys) {
            lockedKeys.remove(key);
            lockedKeys.notifyAll();
        }
    }

    public Object getFromCache(String key) throws InterruptedException {
        try {
            lock(key);

            if (cache.containsKey(key)) {
                // key found in cache - return cache value
            }
            else {
                // retrieve cache value from source, cache it and return
            }

        } finally {
            unlock(key);
        }
    }

}
  • try-finally - is very important - you must guarantee to unlock waiting threads after your operation even if your operation threw exception.
  • It will not work if your back-end is distributed across multiple servers/JVMs.
Anton Fil
  • 223
  • 2
  • 8
-1

Each object has an implicit monitor upon which synchronization works. String object may be created in heap and also may be different for same set of characters (if created by using new) or may be from pool. Two threads will acess the critical section with synchronized block only if they synchronize on same object.

Synchronizing on String literal is really a bad idea. String literal from pool are shared. Just imagine if at two different parts of your code you are having two synchronized sections and you synchronize on two references of String but initilized with string with same set of characters, if String from pool is used then both the places it will be the same object. Even though both the places may have different business context but still you may end up in your application being hanged. It will be very difficult to debug too.

For the specific to the question of will the purpose be solved if synchronization is done on keys.

You want to avoid two threads trying to write without reading the latest value of cache. You will have different key for each entry. Suppose a thread1 and thread2 wants to access the same key then synchronization on the same key object will prevent both of them to enter the synchronized block. Meanwhile if a thread3 wants to access another different key then it can very well do so. Here we see the read and writes are faster as compared to single common object for reads and writes for all keys. So far so good but the problem will arise if suppose you are keeping an array or any other similar non thread safe data structure for storing the cached values. Simultaneous writes (for two or more different keys) can result in one write being overwritten by another at same index.

So it depends upon the implementation of cache data structure how best you can prepare it for faster read and writes in a multi threaded enviornment.

nits.kk
  • 5,204
  • 4
  • 33
  • 55
  • not sure why this was downvoted, but I think it confirms my expectation that the instance must be the same. – Avner Jun 22 '17 at 05:00
  • 1
    @awi I downvoted it because it doesn't address the issue, which is what I stated in my comment. You would be in error to regard this as a correct answer to what you actually asked. – user207421 Jun 22 '17 at 09:26
  • @avvi To an extent I myself agree with EJP, after revisting it I found I missed few things before. I have edited the answer and tried to touch the missed areas previously. – nits.kk Jun 22 '17 at 11:49
  • 1
    @EJP yes on review I agree with your comment that it doesn't make sense to use the keys as set out, but I disagree that it doesn't make sense altogether. It's the implementation that will matter. The essence of my question was not about the cache - that was the example of a critical section. – Avner Jun 23 '17 at 00:08