We're trying to work out the best way to use Hazelcast's IMap without using pessimistic locking.
EntryProcessor seems like the correct choice, however we need to apply two different types of operations: 'create' when containsKey is false, and 'update' when containsKey is true.
How can I utilise EntryProcessor to support these logic checks?
If two threads hit the containsKey() at the same time and it returns false to both of them, I don't want both of them to create the key. I'd want the second thread to apply an update instead.
This is what we have so far:
public void put(String key, Object value) {
IMap<String, Object> map = getMap();
if (!map.containsKey(key)) {
// create key here
} else {
// update existing value here
// ...
map.executeOnKey(key, new TransactionEntryProcessor({my_new_value}));
}
}
private static class MyEntryProcessor implements
EntryProcessor<String, Object>, EntryBackupProcessor<String, Object>, Serializable {
private static final long serialVersionUID = // blah blah
private static final ThreadLocal<Object> entryToSet = new ThreadLocal<>();
MyEntryProcessor(Object entryToSet) {
MyEntryProcessor.entryToSet.set(entryToSet);
}
#Override
public Object process(Map.Entry<String, Object> entry) {
entry.setValue(entryToSet.get());
return entry.getValue();
}
#Override
public EntryBackupProcessor<String, Object> getBackupProcessor() {
return MyEntryProcessor.this;
}
#Override
public void processBackup(Map.Entry<String, Object> entry) {
entry.setValue(entryToSet.get());
}
}
You can see that two threads can enter the put method and call containsKey at the same time. The second will overwrite the outcome of the first.
EntryProcessor by definition is a processing logic that gets executed on the entry itself, eliminating the need of serializing/deserializing the value. Internally, EPs are executed by partition threads, where one partition thread takes care of multiple partitions. When an EP comes to HC, it is picked by the owner thread of the partition where the key belongs. Once the processing is completed, the partition thread is ready to accept and execute other tasks (which may well be same EP for same key, submitted by another thread). Therefore, it may seem so but EPs should not be used as alternatives to pessimistic locking.
If you are insistent and really keen on using EP for this then you could try putting a null check inside process method. Something like this:
public Object process(Map.Entry<String, Object> entry) {
if(null == entry.getValue()) {
entry.setValue("value123");
}
return entry.getValue();
}
This way two things will happen:
1. The other thread will wait for partition thread to be available again
2. Since the value already exists, you wont overwrite anything
Related
I have a question related to synchronization and concurrency in Java.
So I have a method, like this:
private boolean loadData(final Integer fundId, List<Trade> trades) {
synchronized (fundId) {
// do lots of things here and finally load the trades into DB
}
}
Before I made this change, the complete method loadData was synchronized private synchronized boolean loadData. However, my requirement is such that if, lets say, fundId - 1 is processing, then I can allow concurrent processing of any other fundId other than 1.
So, the above code also won't work because the lock would be on the Integer object, hence no other fundId can be concurrently processed.
Is there a way to achieve concurrent processing based on the method parameter ?
You need to create an entry in a ConcurrentHashMap for each value of fundId in order to lock it.
static Map<Integer, Object> locks = new ConcurrentHashMap<>();
private boolean loadData(final Integer fundId, List<Trade> trades){
locks.computeIfAbsent(fundId, k-> { /* your actual job */ return null; });
}
}
Hope that helps !
The function, as it is written, will synchronize on the object fundId, not on Integer. So, it will block if you call the same function from another thread with the same fundId instance. It will not, however, synchronize if you call it with other fundId instances, regardless of the value.
If you need to synchronize based on a value, you can use a shared set of integers (i.e. fundId). Synchronize on the set, and attempt to insert the integer. If it is already in there, someone else is processing that value, so you wait. If it is not there, then you insert it, unlock, process, lock again, remove the value, and signal.
You can achieve that in several ways:
If the class that contains loadData() is called FundLoader you can have a Map<Integer, FundLoader> fundLoaders and each FundLoader is responsible to load the trades for given fundId. The synchronization will be again on method level for loadData
Do a custom synhronization inside loadData
UPDATE - added fundsWaitingForLock to prevent cases when the lock is already taken from the fundLocks map
private final Map<Integer, Object> fundLocks = new HashMap<>();
private final Map<Integer, AtomicInteger> fundsWaitingForLock = new HashMap<>();
private boolean loadData(final Integer fundId, final List<String> trades) {
Object lock;
synchronized (fundLocks) {
lock = fundLocks.computeIfAbsent(fundId, id -> new Object());
fundsWaitingForLock.computeIfAbsent(fundId, id -> new AtomicInteger()).incrementAndGet();
}
synchronized(lock) {
try {
// do lots of things here and finally load the trades into DB
return true;
} finally {
synchronized (fundLocks) {
if (fundsWaitingForLock.get(fundId).decrementAndGet() == 0) {
fundLocks.remove(fundId);
fundsWaitingForLock.remove(fundId);
}
}
}
}
}
Pass a lock instead of fundId.
private boolean loadData(final Lock fundIdLock, final List<String> trades) {
fundIdLock.lock();
try {
// do lots of things here and finally load the trades into DB
} finally {
fundIdLock.unlock();
}
return true;
}
I have a static HashMap that is populated on application startup, and refreshed daily.
How can I ensure that during refresh no other thread can access the map?
#ThreadSafe
public class MyService {
private static final Map<String, Object> map = new HashMap<>();
private MyDao dao;
public void refresh(List<Object> objects) {
map.clear();
map.addAll(dao.findAll()); //maybe long running routine
}
public Object get(String key) {
map.get(key); //ensure this waits during a refresh??
}
}
Should I introduce a simple boolean lock that is set and cleared during refresh()? Or are there better choices? Or is the synchronized mechanism a way to go?
You could use a volatile map and reassign it after population:
public class MyService {
private static volatile Map<String, Object> map = new HashMap<>();
private MyDao dao;
public void refresh(List<Object> objects) {
Map<String, Object> newMap = new HashMap<>();
newMap.addAll(dao.findAll()); //maybe long running routine
map = newMap;
}
public Object get(String key) {
map.get(key); //ensure this waits during a refresh??
}
}
It is non blocking, the assignment from newMap to map is atomic and ensures visibility: any subsequent call to get will be based on the refreshed map.
Performance wise this should work well because volatile reads are almost as fast as normal reads. Volatile writes are a tiny bit slower but considering the refreshing frequency it should not be an issue. If performance matters you should run appropriate tests.
Note: you must make sure that no external code can get access to the map reference, otherwise that code could access stale data.
Please dont make the map-attribute static, all accessor-methods are non-static.
If get should wait or refresh mutates the map instead of completely exchanging it, then ReadWriteLock is the way to go. ConcurrentMap if the collection is mutated but get should not wait.
But if refresh completely replaces the map, i may suggest different non-waiting implementations:
1) do the long running operation outside the synchronized block
public void refresh() {
Map<String, Object> objs = dao.findAll();
synchronized(this) {
map.clear();
map.addAll(objs);
}
}
public Object get(String key) {
synchronized(this) {
return map.get(key);
}
}
The readers are not run in parallel, but else perfectly valid.
2) use a volatile non-final reference of an nonchanged collection:
// guava's ImmutableHashMap instead of Map would be even better
private volatile Map<String, Object> map = new HashMap<>();
public void refresh() {
Map<String, Object> map = dao.findAll();
this.map = map;
}
3) AtomicReference of nonchanged collection
Instead of a volatile reference also a AtomicReference may be used. Probably better because more explicit than the easily missed volatile.
// guava's ImmutableHashMap instead of Map would be even better
private final AtomicReference<Map<String, Object>> mapRef =
new AtomicReference<>(new HashMap<String, Object>());
public void refresh() {
mapRef.set(dao.findAll());
}
public Object get(String key) {
return map.get().get(key);
}
Using synchronized block or a ReadWriteLock would be a better choice here. This way, you wouldn't have to change anything in the calling code.
You could also use a concurrentHash, but in that case, for aggregate operations such as putAll and clear, concurrent retrievals may reflect insertion or removal of only some entries.
It's weird you need to clear() then addAll() for such a global map. I smell your problem needs to be resolved properly by a ReadWriteLock protected double buffering.
Anyway, from a pure performance point of view, on normal server boxes with total number of CPU core < 32, and much more read than write, ConcurrentHashMap is probably your best choice. Otherwise it needs to be studied case by case.
I'm looking for a way to synchronize a method based on the parameter it receives, something like this:
public synchronized void doSomething(name){
//some code
}
I want the method doSomething to be synchronized based on the name parameter like this:
Thread 1: doSomething("a");
Thread 2: doSomething("b");
Thread 3: doSomething("c");
Thread 4: doSomething("a");
Thread 1 , Thread 2 and Thread 3 will execute the code without being synchronized , but Thread 4 will wait until Thread 1 has finished the code because it has the same "a" value.
Thanks
UPDATE
Based on Tudor explanation I think I'm facing another problem:
here is a sample of the new code:
private HashMap locks=new HashMap();
public void doSomething(String name){
locks.put(name,new Object());
synchronized(locks.get(name)) {
// ...
}
locks.remove(name);
}
The reason why I don't populate the locks map is because name can have any value.
Based on the sample above , the problem can appear when adding / deleting values from the hashmap by multiple threads in the same time, since HashMap is not thread-safe.
So my question is if I make the HashMap a ConcurrentHashMap which is thread safe, will the synchronized block stop other threads from accessing locks.get(name) ??
TL;DR:
I use ConcurrentReferenceHashMap from the Spring Framework. Please check the code below.
Although this thread is old, it is still interesting. Therefore, I would like to share my approach with Spring Framework.
What we are trying to implement is called named mutex/lock. As suggested by Tudor's answer, the idea is to have a Map to store the lock name and the lock object. The code will look like below (I copy it from his answer):
Map<String, Object> locks = new HashMap<String, Object>();
locks.put("a", new Object());
locks.put("b", new Object());
However, this approach has 2 drawbacks:
The OP already pointed out the first one: how to synchronize the access to the locks hash map?
How to remove some locks which are not necessary anymore? Otherwise, the locks hash map will keep growing.
The first problem can be solved by using ConcurrentHashMap. For the second problem, we have 2 options: manually check and remove locks from the map, or somehow let the garbage collector knows which locks are no longer used and the GC will remove them. I will go with the second way.
When we use HashMap, or ConcurrentHashMap, it creates strong references. To implement the solution discussed above, weak references should be used instead (to understand what is a strong/weak reference, please refer to this article or this post).
So, I use ConcurrentReferenceHashMap from the Spring Framework. As described in the documentation:
A ConcurrentHashMap that uses soft or weak references for both keys
and values.
This class can be used as an alternative to
Collections.synchronizedMap(new WeakHashMap<K, Reference<V>>()) in
order to support better performance when accessed concurrently. This
implementation follows the same design constraints as
ConcurrentHashMap with the exception that null values and null keys
are supported.
Here is my code. The MutexFactory manages all the locks with <K> is the type of the key.
#Component
public class MutexFactory<K> {
private ConcurrentReferenceHashMap<K, Object> map;
public MutexFactory() {
this.map = new ConcurrentReferenceHashMap<>();
}
public Object getMutex(K key) {
return this.map.compute(key, (k, v) -> v == null ? new Object() : v);
}
}
Usage:
#Autowired
private MutexFactory<String> mutexFactory;
public void doSomething(String name){
synchronized(mutexFactory.getMutex(name)) {
// ...
}
}
Unit test (this test uses the awaitility library for some methods, e.g. await(), atMost(), until()):
public class MutexFactoryTests {
private final int THREAD_COUNT = 16;
#Test
public void singleKeyTest() {
MutexFactory<String> mutexFactory = new MutexFactory<>();
String id = UUID.randomUUID().toString();
final int[] count = {0};
IntStream.range(0, THREAD_COUNT)
.parallel()
.forEach(i -> {
synchronized (mutexFactory.getMutex(id)) {
count[0]++;
}
});
await().atMost(5, TimeUnit.SECONDS)
.until(() -> count[0] == THREAD_COUNT);
Assert.assertEquals(count[0], THREAD_COUNT);
}
}
Use a map to associate strings with lock objects:
Map<String, Object> locks = new HashMap<String, Object>();
locks.put("a", new Object());
locks.put("b", new Object());
// etc.
then:
public void doSomething(String name){
synchronized(locks.get(name)) {
// ...
}
}
The answer of Tudor is fine, but it's static and not scalable. My solution is dynamic and scalable, but it goes with increased complexity in the implementation. The outside world can use this class just like using a Lock, as this class implements the interface. You get an instance of a parameterized lock by the factory method getCanonicalParameterLock.
package lock;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public final class ParameterLock implements Lock {
/** Holds a WeakKeyLockPair for each parameter. The mapping may be deleted upon garbage collection
* if the canonical key is not strongly referenced anymore (by the threads using the Lock). */
private static final Map<Object, WeakKeyLockPair> locks = new WeakHashMap<>();
private final Object key;
private final Lock lock;
private ParameterLock (Object key, Lock lock) {
this.key = key;
this.lock = lock;
}
private static final class WeakKeyLockPair {
/** The weakly-referenced parameter. If it were strongly referenced, the entries of
* the lock Map would never be garbage collected, causing a memory leak. */
private final Reference<Object> param;
/** The actual lock object on which threads will synchronize. */
private final Lock lock;
private WeakKeyLockPair (Object param, Lock lock) {
this.param = new WeakReference<>(param);
this.lock = lock;
}
}
public static Lock getCanonicalParameterLock (Object param) {
Object canonical = null;
Lock lock = null;
synchronized (locks) {
WeakKeyLockPair pair = locks.get(param);
if (pair != null) {
canonical = pair.param.get(); // could return null!
}
if (canonical == null) { // no such entry or the reference was cleared in the meantime
canonical = param; // the first thread (the current thread) delivers the new canonical key
pair = new WeakKeyLockPair(canonical, new ReentrantLock());
locks.put(canonical, pair);
}
}
// the canonical key is strongly referenced now...
lock = locks.get(canonical).lock; // ...so this is guaranteed not to return null
// ... but the key must be kept strongly referenced after this method returns,
// so wrap it in the Lock implementation, which a thread of course needs
// to be able to synchronize. This enforces a thread to have a strong reference
// to the key, while it isn't aware of it (as this method declares to return a
// Lock rather than a ParameterLock).
return new ParameterLock(canonical, lock);
}
#Override
public void lock() {
lock.lock();
}
#Override
public void lockInterruptibly() throws InterruptedException {
lock.lockInterruptibly();
}
#Override
public boolean tryLock() {
return lock.tryLock();
}
#Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return lock.tryLock(time, unit);
}
#Override
public void unlock() {
lock.unlock();
}
#Override
public Condition newCondition() {
return lock.newCondition();
}
}
Of course you'd need a canonical key for a given parameter, otherwise threads would not be synchronized as they would be using a different Lock. Canonicalization is the equivalent of the internalization of Strings in Tudor's solution. Where String.intern() is itself thread-safe, my 'canonical pool' is not, so I need extra synchronization on the WeakHashMap.
This solution works for any type of Object. However, make sure to implement equals and hashCode correctly in custom classes, because if not, threading issues will arise as multiple threads could be using different Lock objects to synchronize on!
The choice for a WeakHashMap is explained by the ease of memory management it brings. How else could one know that no thread is using a particular Lock anymore? And if this could be known, how could you safely delete the entry out of the Map? You would need to synchronize upon deletion, because you have a race condition between an arriving thread wanting to use the Lock, and the action of deleting the Lock from the Map. All these things are just solved by using weak references, so the VM does the work for you, and this simplifies the implementation a lot. If you inspected the API of WeakReference, you would find that relying on weak references is thread-safe.
Now inspect this test program (you need to run it from inside the ParameterLock class, due to private visibility of some fields):
public static void main(String[] args) {
Runnable run1 = new Runnable() {
#Override
public void run() {
sync(new Integer(5));
System.gc();
}
};
Runnable run2 = new Runnable() {
#Override
public void run() {
sync(new Integer(5));
System.gc();
}
};
Thread t1 = new Thread(run1);
Thread t2 = new Thread(run2);
t1.start();
t2.start();
try {
t1.join();
t2.join();
while (locks.size() != 0) {
System.gc();
System.out.println(locks);
}
System.out.println("FINISHED!");
} catch (InterruptedException ex) {
// those threads won't be interrupted
}
}
private static void sync (Object param) {
Lock lock = ParameterLock.getCanonicalParameterLock(param);
lock.lock();
try {
System.out.println("Thread="+Thread.currentThread().getName()+", lock=" + ((ParameterLock) lock).lock);
// do some work while having the lock
} finally {
lock.unlock();
}
}
Chances are very high that you would see that both threads are using the same lock object, and so they are synchronized. Example output:
Thread=Thread-0, lock=java.util.concurrent.locks.ReentrantLock#8965fb[Locked by thread Thread-0]
Thread=Thread-1, lock=java.util.concurrent.locks.ReentrantLock#8965fb[Locked by thread Thread-1]
FINISHED!
However, with some chance it might be that the 2 threads do not overlap in execution, and therefore it is not required that they use the same lock. You could easily enforce this behavior in debugging mode by setting breakpoints at the right locations, forcing the first or second thread to stop wherever necessary. You will also notice that after the Garbage Collection on the main thread, the WeakHashMap will be cleared, which is of course correct, as the main thread waited for both worker threads to finish their job by calling Thread.join() before calling the garbage collector. This indeed means that no strong reference to the (Parameter)Lock can exist anymore inside a worker thread, so the reference can be cleared from the weak hashmap. If another thread now wants to synchronize on the same parameter, a new Lock will be created in the synchronized part in getCanonicalParameterLock.
Now repeat the test with any pair that has the same canonical representation (= they are equal, so a.equals(b)), and see that it still works:
sync("a");
sync(new String("a"))
sync(new Boolean(true));
sync(new Boolean(true));
etc.
Basically, this class offers you the following functionality:
Parameterized synchronization
Encapsulated memory management
The ability to work with any type of object (under the condition that equals and hashCode is implemented properly)
Implements the Lock interface
This Lock implementation has been tested by modifying an ArrayList concurrently with 10 threads iterating 1000 times, doing this: adding 2 items, then deleting the last found list entry by iterating the full list. A lock is requested per iteration, so in total 10*1000 locks will be requested. No ConcurrentModificationException was thrown, and after all worker threads have finished the total amount of items was 10*1000. On every single modification, a lock was requested by calling ParameterLock.getCanonicalParameterLock(new String("a")), so a new parameter object is used to test the correctness of the canonicalization.
Please note that you shouldn't be using String literals and primitive types for parameters. As String literals are automatically interned, they always have a strong reference, and so if the first thread arrives with a String literal for its parameter then the lock pool will never be freed from the entry, which is a memory leak. The same story goes for autoboxing primitives: e.g. Integer has a caching mechanism that will reuse existing Integer objects during the process of autoboxing, also causing a strong reference to exist. Addressing this, however, this is a different story.
Check out this framework. Seems you're looking for something like this.
public class WeatherServiceProxy {
...
private final KeyLockManager lockManager = KeyLockManagers.newManager();
public void updateWeatherData(String cityName, Date samplingTime, float temperature) {
lockManager.executeLocked(cityName, new LockCallback() {
public void doInLock() {
delegate.updateWeatherData(cityName, samplingTime, temperature);
}
});
}
https://code.google.com/p/jkeylockmanager/
I've created a tokenProvider based on the IdMutexProvider of McDowell.
The manager uses a WeakHashMap which takes care of cleaning up unused locks.
You could find my implementation here.
I've found a proper answer through another stackoverflow question: How to acquire a lock by a key
I copied the answer here:
Guava has something like this being released in 13.0; you can get it out of HEAD if you like.
Striped more or less allocates a specific number of locks, and then assigns strings to locks based on their hash code. The API looks more or less like
Striped<Lock> locks = Striped.lock(stripes);
Lock l = locks.get(string);
l.lock();
try {
// do stuff
} finally {
l.unlock();
}
More or less, the controllable number of stripes lets you trade concurrency against memory usage, because allocating a full lock for each string key can get expensive; essentially, you only get lock contention when you get hash collisions, which are (predictably) rare.
Just extending on to Triet Doan's answer, we also need to take care of if the MutexFactory can be used at multiple places, as with currently suggested code we will end up with same MutexFactory at all places of its usage.
For example:-
#Autowired
MutexFactory<CustomObject1> mutexFactory1;
#Autowired
MutexFactory<CustomObject2> mutexFactory2;
Both mutexFactory1 & mutexFactory2 will refer to the same instance of factory even if their type differs, this is due to the fact that a single instance of MutexFactory is created by spring during application startup and same is used for both mutexFactory1 & mutexFactory2.
So here is the extra Scope annotation that needs to be put in to avoid above case-
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MutexFactory<K> {
private ConcurrentReferenceHashMap<K, Object> map;
public MutexFactory() {
this.map = new ConcurrentReferenceHashMap<>();
}
public Object getMutex(K key) {
return this.map.compute(key, (k, v) -> v == null ? new Object() : v);
}
}
I've used a cache to store lock objects. The my cache will expire objects after a period, which really only needs to be longer that the time it takes the synchronized process to run
`
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
...
private final Cache<String, Object> mediapackageLockCache = CacheBuilder.newBuilder().expireAfterWrite(DEFAULT_CACHE_EXPIRE, TimeUnit.SECONDS).build();
...
public void doSomething(foo) {
Object lock = mediapackageLockCache.getIfPresent(foo.toSting());
if (lock == null) {
lock = new Object();
mediapackageLockCache.put(foo.toString(), lock);
}
synchronized(lock) {
// execute code on foo
...
}
}
`
I have a much simpler, scalable implementation akin to #timmons post taking advantage of guavas LoadingCache with weakValues. You will want to read the help files on "equality" to understand the suggestion I have made.
Define the following weakValued cache.
private final LoadingCache<String,String> syncStrings = CacheBuilder.newBuilder().weakValues().build(new CacheLoader<String, String>() {
public String load(String x) throws ExecutionException {
return new String(x);
}
});
public void doSomething(String x) {
x = syncStrings.get(x);
synchronized(x) {
..... // whatever it is you want to do
}
}
Now! As a result of the JVM, we do not have to worry that the cache is growing too large, it only holds the cached strings as long as necessary and the garbage manager/guava does the heavy lifting.
I often enough want to access (and possibly add/remove) elements of a given ConcurrentMap so that only one thread can access any single key at a time. What is the best way to do this? Synchronizing on the key itself doesn't work: other threads might access the same key via an equal instance.
It's good enough if the answer only works with the maps built by guava MapMaker.
See a simple solution here Simple Java name based locks?
EDIT: This solution has a clear happens-before relation from unlock to lock. However, the next solution, now withdrawn, doesn't. The ConcurrentMap javadoc is too light to guaranteed that.
(Withdrawn) If you want to reuse your map as a lock pool,
private final V LOCK = ...; // a fake value
// if a key is mapped to LOCK, that means the key is locked
ConcurrentMap<K,V> map = ...;
V lock(key)
V value;
while( (value=map.putIfAbsent(key, LOCK))==LOCK )
// another thread locked it before me
wait();
// now putIfAbsent() returns a real value, or null
// and I just sucessfully put LOCK in it
// I am now the lock owner of this key
return value; // for caller to work on
// only the lock owner of the key should call this method
unlock(key, value)
// I put a LOCK on the key to stall others
// now I just need to swap it back with the real value
if(value!=null)
map.put(key, value);
else // map doesn't accept null value
map.remove(key)
notifyAll();
test()
V value = lock(key);
// work on value
// unlock.
// we have a chance to specify a new value here for the next worker
newValue = ...; // null if we want to remove the key from map
unlock(key, newValue); // in finally{}
This is quite messy because we reuse the map for two difference purposes. It's better to have lock pool as a separate data structure, leave map simply as the k-v storage.
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 void doSynchronouslyOnlyForEqualKeys(String key) throws InterruptedException {
try {
lock(key);
//Put your code here.
//For different keys it is executed in parallel.
//For equal keys it is executed synchronously.
} finally {
unlock(key);
}
}
key can be not only a 'String' but any class with correctly overridden 'equals' and 'hashCode' methods.
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.
Can't you just create you own class that extends concurrentmap.
Override the get(Object key) method, so it checks if the requested key object is already 'checked out' by another thread ?
You'll also have to make a new method in your concurrentmap that 'returns' the items to the map, so they are available again to another thread.
I need to store a lookup map in memory on a servlet. The map should be loaded from a file, and whenever the file is updated (which is not very often), the map should be reloaded in the same thread that is doing the lookup.
But I'm not sure how to implement this functionality in a thread safe manner. I want to make sure that the reload does not happen more than once.
public class LookupTable
{
private File file;
private long mapLastUpdate;
private Map<String, String> map;
public String getValue(String key)
{
long fileLastUpdate = file.lastModified();
if (fileLastUpdate > mapLastUpdate)
{
// Only the first thread should run the code in the synchronized block.
// The other threads will wait until it is finished. Then skip it.
synchronized (this)
{
Map newMap = loadMap();
this.map = newMap;
this.mapLastUpdate = fileLastUpdate;
}
}
return map.get(key);
}
private Map<String, String> loadMap()
{
// Load map from file.
return null;
}
}
If anyone has any suggestions on external libraries that has solved this already, that would work too. I took a quick look at some caching libraries, but I couldn't find what I needed.
Thanks
I would suggest you using imcache. Please a build a concurrent cache with cache loader as follows,
Cache<String,LookupTable> lookupTableCache = CacheBuilder.
concurrentHeapCache().cacheLoader(new CacheLoader<String, LookupTable>() {
public LookupTable load(String key) {
//code to load item from file.
}
}).build();
As suggested by z5h, you need to protect your condition (fileLastUpdate > mapsLastUpdate) by the same lock that is used to keep the file reloading atomic.
The way I think about this stuff is to look at all of the member variables in the class and figure out what thread-safety guarantees they need. In your case, none of the members (File, long, HashMap -- ok, I'm assuming HashMap) are thread safe, and thus they must all be protected by a lock. They're also all involved in an invariant (they all change together) together, so they must be protected by the SAME lock.
Your code, updated, and using the annotations (these are just info, they don't enforce anything!) suggested by Java Concurrency In Practice (an excellent book all Java devs should read :))
/**
* Lookup table that automatically reloads itself from a file
* when the filechanges.
*/
#ThreadSafe
public class LookupTable
{
#GuardedBy("this")
private long mapLastUpdate;
#GuardedBy("this")
private final File file;
#GuardedBy("this")
private Map<String, String> map;
public LookupTable(File file)
{
this.file = file;
this.map = loadMap()
}
public synchronized String getValue(String key)
{
long fileLastUpdate = file.lastModified();
if (fileLastUpdate > this.mapLastUpdate)
{
// Only the first thread should run the code in the synchronized block.
// The other threads will wait until it is finished. Then skip it.
Map newMap = loadMap();
this.map = newMap;
this.mapLastUpdate = fileLastUpdate;
}
return map.get(key);
}
private synchronized Map<String, String> loadMap()
{
// Load map from file.
return null;
}
}
This will be safe, but it is fully synchronized: only one thread doing a lookup in the map at once. If you need concurrency on the lookups, you'll need a more sophisticated scheme. Implementation would depend on whether threads are allowed to see the old version of the lookup table while the new one is loading, among other things.
If you made the map member final, and protected it with a ReadWriteLock, you might get some bang. It's hard to predict how much contention you might have on this lock from the limited info here.
Your check needs to be in the synchronized block.
Otherwise several threads could read (fileLastUpdate > mapLastUpdate) as true, then all block on the update code. Worst of both worlds.