问题
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
}
}
}
}
回答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.
回答2:
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.
来源:https://stackoverflow.com/questions/44688589/use-of-a-key-to-synchronize-access-to-code-block