Thread safe Java pool, with INSTANT READ - java

To write a fully functional pool of Java objects, using READ/WRITE locks is not a big problem.
The problem I see is that READ operation will have to wait until the storage monitor (or something similar, depending on the model) is released, which really slows it.
So, the following requirements should be met:
READ (or GET) operation should be INSTANT - using some key, the latest version of the object should be returned immediately, without waiting for any lock.
WRITE (CREATE/UPDATE) - may be queued, reasonably delayed in time, probably waiting for some storage lock.
Any code sample?
I didn't find a question that directly targets the issue.
It popped up in some discussions, but I couldn't find a question that was fully devoted to the problems of creating such a pool in Java.

when the modification on the datastructure takes too long (for whatever reason), simply waiting and write-locking the structure will not be successful. You just cannot foresee when you will have enough time to perform the modification without blocking any reads.
the only thing you can do (try to do) is to reduce the time within the write-operation to a minimum. As #assylias stated, a CopyOnWrite* does this by cloning the datastructure upon write operations and atomically activates the modified structure when the operation is complete.
By this the read-locks will take as long as the duration of the clone-operation plus the time for switching the reference. You can work that down to small parts of the datastructure: if only states in an object change, you can modify a copy of that object and change the reference in your more complex datastructure to that copy afterwards.
The other way around is to do that copy on or before read operations. Often you return a copy of an Object via the API of you datastructure anyway, so just "cache" that copy and during the modifications let the readers access the cached copy. This is what database-caches aso do.
It depends on your model what is best for you. If you will have few writes on data that can be copied easily, CopyOnWrite will probably perform best. If you will have lots of writes you probably better provide a single "read"/cached-state of your structure and switch it from time to time.
AtomicReference<Some> datastructure = ...;
//copy on write
synchronized /*one writer*/ void change(Object modification)
throws CloneNotSupportedException {
Object copy = datastructure.clone();
apply(copy, modification);
datastructure.set(copy);
}
Object search(Object select) {
return datastructure.get().search(select);
}
// copy for read
AtomicReference<Some> cached = new AtomicReference<Some>(datastructure.get().clone());
synchronized void change(Object modification) {
apply(datastructure, modification);
cached.set(datastructure);
}
Object search(Object select) {
return cached.get().search(select);
}
For both operations there is no wait when reading .. but for the time it needs to switch the reference.

In this case you can simply use a volatile variable to avoid locking on the reader side and keep the writes exclusive with a synchronized method. volatile will add little to no overhead to reads but writes will be a little slow. This might be a good solution depending on expected throughput and read/write ratio.
class Cache {
private volatile Map<K, V> cache; //Assuming map is the right data structure
public V get(K key) {
return cache.get(key);
}
//synchronized writes for exclusive access
public synchronized void put(K key, V value) {
Map<K, V> copy = new HashMap<> (cache);
V value = copy.put(key, value);
//volatile guarantees that this will be visible from the getter
cache = copy;
return value;
}
}

Here is a totally-lock-free Java object pool solution. FYI
http://daviddengcn.blogspot.com/2015/02/a-lock-free-java-object-pool.html

Related

Can a ConcurrentHashMap be externally synchronized in Java?

I am using a ConcurrentHashMap, and I need to iterate over all its elements when calculating a new element that is not present yet and do some other modifications possibly over the same map.
I wanted those operations be atomic, and block the ConcurrentHashMap to prevent from getting an exception derived from concurrency.
The solution I programmed was to synchronize the ConcurrentHashMap object with itself as lock, but Sonar reports a major issue, so I do not know whether that solution is correct
Proposed code:
Modification to the original text
public class MyClass<K, V> {
ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();
public V get(K key) {
return map.computeIfAbsent(key, this::calculateNewElement);
}
protected V calculateNewElement(K key) {
V result;
// the following line throws the sonar issue:
synchronized(map) {
// calculation of the new element (assignating it to result)
// with iterations over the whole map
// and possibly with other modifications over the same map
}
return result;
}
}
This code triggers a Sonar major issue:
Multi-threading - Synchronization performed on util.concurrent
instance
findbugs:JLM_JSR166_UTILCONCURRENT_MONITORENTER
This method performs synchronization on an object that is an instance
of a class from the java.util.concurrent package (or its subclasses).
Instances of these classes have their own concurrency control
mechanisms that are orthogonal to the synchronization provided by the
Java keyword synchronized. For example, synchronizing on an
AtomicBoolean will not prevent other threads from modifying the
AtomicBoolean.
Such code may be correct, but should be carefully reviewed and
documented, and may confuse people who have to maintain the code at a
later date.
If you have to change many nodes for each update, maybe you're using the wrong data structure. Check out concurrent implementations of trees. A persistent collection (that provides immutability plus fast updates) would seem ideal.
There is a method provided for atomic updates:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ConcurrentHashMap.html#compute(K,java.util.function.BiFunction)
ConcurrentHashMap is built to allow a high degree of concurrent access. (See this article describing its inner workings) If an update is made to an entry using the provided means (such as compute, computeIfPresent, etc.) that should lock only the segment the entry is in, not the whole thing.
When you lock the whole map for an update you're not getting the benefit of using this specialized data structure. That's what Sonar is complaining about.
There is also the issue that readers have to do locking too, updater threads aren't the only ones that need to lock. This kind of thing is why CHM was invented in the first place.
https://www.amazon.com.tr/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601.
"Concurrent objects do not support 'client side-locking'"
You could perform client side-locking on synchronized collections like.
final List<Type> synchronizedList = Collections.synchronizedList(new ArrayList<>()); //do not use another reference to internal array list and access the list using through synchronizedList reference.
In this case, you could use;
synchronized(synchronizedList){
//do something with synchronized list.
}
NOTE: This might perform badly, namely introduce scability issues because the code is highly serialized. (Amdal's Law).
Concurrent objects are designed for scability. Maybe you can take a snapshot of the map to another 'local' collections and do operations on them. Or you can directly use map without any synchronization. (In this case, some new elements could be added or deleted and your iterator might or might not reflect those changes)
"ConcurrentHashMap, along with the other concurrent collections, further improve on the synchronized collection classes by providing iterators that do not throw ConcurrentModificationException, thus eliminating the need to lock the collection during iteration. The iterators returned by ConcurrentHashMap are weakly consistent instead of fail-fast. A weakly consistent iterator can tolerate concurrent modification, traverses elements as they existed when the iterator was constructed, and may (but is not guaranteed to) reflect modifications to the collection after the construction of the iterator."
There are operations on ConcurrentHashMap which allows you to perform atomic operations on a specific key like compute, computeIfAbsent, computeIfPresent.
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html
You could replace your combination of synchronization and access to the map with a "regular" collection and the use of a ReadWriteLock (e.g. the java.util.concurrent.locks.ReentrantReadWriteLock)
See this part of the description of the java.util.concurrent package:
The "Concurrent" prefix used with some classes in this package is a shorthand indicating several differences from similar "synchronized" classes. For example java.util.Hashtable and Collections.synchronizedMap(new HashMap()) are synchronized. But ConcurrentHashMap is "concurrent". A concurrent collection is thread-safe, but not governed by a single exclusion lock. In the particular case of ConcurrentHashMap, it safely permits any number of concurrent reads as well as a large number of concurrent writes. "Synchronized" classes can be useful when you need to prevent all access to a collection via a single lock, at the expense of poorer scalability. In other cases in which multiple threads are expected to access a common collection, "concurrent" versions are normally preferable. And unsynchronized collections are preferable when either collections are unshared, or are accessible only when holding other locks.
From the docs of ReadWriteLock:
A ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writing. The read lock may be held simultaneously by multiple reader threads, so long as there are no writers. The write lock is exclusive.
The "reentrant" implementation mimicks the behaviour of a synchronized block:
(from the docs of ReentrantLock)
A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities.
Your code for it could look like this:
public class MyClass<K, V> {
private final Map<K, V> map = new HashMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
public V get(K key) {
readLock.lock();
try {
return map.computeIfAbsent(key, this::calculateNewElement);
} finally {
readLock.unlock();
}
}
protected V calculateNewElement(K key) {
readLock.unlock();
writeLock.lock();
try {
V result;
// calculation of the new element (assigning it to result)
// with iterations over the whole map
// and possibly with other modifications over the same map
return result;
} finally {
writeLock.unlock();
}
}
public V put(K key, V value) {
writeLock.lock();
try {
return map.put(key, value);
} finally {
writeLock.unlock();
}
}
}
With this implementation reads are blocked while a write is happening and vice versa. Multiple reads are still possible at the same time, but write is exclusive.
But you have to take care that the map doesn't "escape" the object and is accessed somehow differently - also inside the class you have to protect all the access to the map with the lock.
The JavaDocs of ReentrantReadWriteLock provides you with examples and some conditions you should be aware of (e.g. a size limit of locks).
Thanks to all answers, I could finally program a solution.
public class MyClass<K, V> {
ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();
public V get(K key) {
V result = map.get(key);
if(result == null) {
result = calculateNewElement(key);
}
return
}
public synchronized void put(K key, V value) {
map.put(key, value);
}
protected synchronized V calculateNewElement(K key) {
V result = map.get(key);
if(result == null) {
// calculation of the new element (assignating it to result)
// with iterations over the whole map
// and possibly with other modifications over the same map
put(key, result);
}
return result;
}
}
I will describe the problem a little more:
Description of the particular problem that the solucion tries to solve:
K has two attributes which are objects of Class<?> type (let's call them originClass and destinationClass),
and V is a translator from a Pojo with a originClass ( or one of its superclasses) to a Pojo with destinationClass (or one of its derived classes), so it fits the translation originClass -> destinationClass
and when V is not found for a particular K, then
calculateNewElement function tries to look for a non direct path between an originClass and destinationClass,
(that means that we might have a key (K0) with our originClass and a destinationClass(0)
and another key (K1) that has an origin(1) equals to destinationClass(0), and a destinationClass(1), which is a derived class of our destinationClass).
That could lead to a new V that is a Combination of the Keys:
K0 ( originClass(0) = originClass, destinationClass(0) ) (V0) -->
K1 ( originClass(1) = destinationClass(0), destinationClass(1) (a derived class of destinationClass) ) (V1) -->
K2 ( originClass(2) = destinationClass(1), destinationClass(2) = destinationClass) (direct translation that does not exist in the map)
We could thing of joining K1 and K2 into a new_K, this way:
put( new_K(originClass(1), destinationClass), V1 ) // this is the new different key put inside calculateNewElement
then the new V (of our original K) created by calculateNewElement would be a combination of K0 and new_K:
V v = new VCombination(K0, new_K), which also will be put by our calculateNewElement function
As in my case put function is called seldom (only during initialization), the synchronization is acceptable.
This scenario fits with the limitations that Holger metioned below. That in my case do not apply, due to the nature of the particular problem:
Only when a "halfway" element did not exactly exist yet then calculateNewElement function will put it in the map
The new elements (which are a combination of existing ones) only need that the elements of the combination exist in the map, so it is not allowed to remove elements from Map (only clearing it would be acceptable)

Double checked locking with regular HashMap

Back to concurrency. By now it is clear that for the double checked locking to work the variable needs to be declared as volatile. But then what if double checked locking is used as below.
class Test<A, B> {
private final Map<A, B> map = new HashMap<>();
public B fetch(A key, Function<A, B> loader) {
B value = map.get(key);
if (value == null) {
synchronized (this) {
value = map.get(key);
if (value == null) {
value = loader.apply(key);
map.put(key, value);
}
}
}
return value;
}
}
Why does it really have to be a ConcurrentHashMap and not a regular HashMap? All map modification is done within the synchronized block and the code doesn't use iterators so technically there should be no "concurrent modification" problems.
Please avoid suggesting the use of putIfAbsent/computeIfAbsent as I am asking about the concept and not the use of API :) unless using this API contributes to HashMap vs ConcurrentHashMap subject.
Update 2016-12-30
This question was answered by a comment below by Holger "HashMap.get doesn’t modify the structure, but your invocation of put does. Since there is an invocation of get outside of the synchronized block, it can see an incomplete state of a put operation happening concurrently." Thanks!
This question is muddled on so many counts that its hard to answer.
If this code is only ever called from a single thread, then you're making it too complicated; you don't need any synchronization. But clearly that's not your intention.
So, multiple threads will call the fetch method, which delegates to HashMap.get() without any synchronization. HashMap is not thread-safe. Bam, end of story. Doesn't even matter if you're trying to simulate double-checked locking; the reality is that calling get() and put() on a map will manipulate the internal mutable data structures of the HashMap, without consistent synchronization on all code paths, and since you can be calling these concurrently from multiple threads, you're already dead.
(Also, you probably think that HashMap.get() is a pure read operation, but that's wrong too. What if the HashMap is actually a LinkedHashMap (which is a subclass of HashMap.) LinkedHashMap.get() will update the access order, which involves writing to internal data structures -- here, concurrently without synchronization. But even if get() is doing no writing, your code here is still broken.)
Rule of thumb: when you think you have a clever trick that lets you avoid synchronizing, you're almost certainly wrong.

How to optimize concurrent operations in Java?

I'm still quite shaky on multi-threading in Java. What I describe here is at the very heart of my application and I need to get this right. The solution needs to work fast and it needs to be practically safe. Will this work? Any suggestions/criticism/alternative solutions welcome.
Objects used within my application are somewhat expensive to generate but change rarely, so I am caching them in *.temp files. It is possible for one thread to try and retrieve a given object from cache, while another is trying to update it there. Cache operations of retrieve and store are encapsulated within a CacheService implementation.
Consider this scenario:
Thread 1: retrieve cache for objectId "page_1".
Thread 2: update cache for objectId "page_1".
Thread 3: retrieve cache for objectId "page_2".
Thread 4: retrieve cache for objectId "page_3".
Thread 5: retrieve cache for objectId "page_4".
Note: thread 1 appears to retrieve an obsolete object, because thread 2 has a newer copy of it. This is perfectly OK so I do not need any logic that will give thread 2 priority.
If I synchronize retrieve/store methods on my service, then I'm unnecessarily slowing things down for threads 3, 4 and 5. Multiple retrieve operations will be effective at any given time but the update operation will be called rarely. This is why I want to avoid method synchronization.
I gather I need to synchronize on an object that is exclusively common to thread 1 and 2, which implies a lock object registry. Here, an obvious choice would be a Hashtable but again, operations on Hashtable are synchronized, so I'm trying a HashMap. The map stores a string object to be used as a lock object for synchronization and the key/value would be the id of the object being cached. So for object "page_1" the key would be "page_1" and the lock object would be a string with a value of "page_1".
If I've got the registry right, then additionally I want to protect it from being flooded with too many entries. Let's not get into details why. Let's just assume, that if the registry has grown past defined limit, it needs to be reinitialized with 0 elements. This is a bit of a risk with an unsynchronized HashMap but this flooding would be something that is outside of normal application operation. It should be a very rare occurrence and hopefully never takes place. But since it is possible, I want to protect myself from it.
#Service
public class CacheServiceImpl implements CacheService {
private static ConcurrentHashMap<String, String> objectLockRegistry=new ConcurrentHashMap<>();
public Object getObject(String objectId) {
String objectLock=getObjectLock(objectId);
if(objectLock!=null) {
synchronized(objectLock) {
// read object from objectInputStream
}
}
public boolean storeObject(String objectId, Object object) {
String objectLock=getObjectLock(objectId);
synchronized(objectLock) {
// write object to objectOutputStream
}
}
private String getObjectLock(String objectId) {
int objectLockRegistryMaxSize=100_000;
// reinitialize registry if necessary
if(objectLockRegistry.size()>objectLockRegistryMaxSize) {
// hoping to never reach this point but it is not impossible to get here
synchronized(objectLockRegistry) {
if(objectLockRegistry.size()>objectLockRegistryMaxSize) {
objectLockRegistry.clear();
}
}
}
// add lock to registry if necessary
objectLockRegistry.putIfAbsent(objectId, new String(objectId));
String objectLock=objectLockRegistry.get(objectId);
return objectLock;
}
If you are reading from disk, lock contention is not going to be your performance issue.
You can have both threads grab the lock for the entire cache, do a read, if the value is missing, release the lock, read from disk, acquire the lock, and then if the value is still missing write it, otherwise return the value that is now there.
The only issue you will have with that is the concurrent read trashing the disk... but the OS caches will be hot, so the disk shouldn't be overly trashed.
If that is an issue then switch your cache to holding a Future<V> in place of a <V>.
The get method will become something like:
public V get(K key) {
Future<V> future;
synchronized(this) {
future = backingCache.get(key);
if (future == null) {
future = executorService.submit(new LoadFromDisk(key));
backingCache.put(key, future);
}
}
return future.get();
}
Yes that is a global lock... but you're reading from disk, and don't optimize until you have a proved performance bottleneck...
Oh. First optimization, replace the map with a ConcurrentHashMap and use putIfAbsent and you'll have no lock at all! (BUT only do that when you know this is an issue)
The complexity of your scheme has already been discussed. That leads to hard to find bugs. For example, not only do you lock on non-final variables, but you even change them in the middle of synchronized blocks that use them as a lock. Multi-threading is very hard to reason about, this kind of code makes it almost impossible:
synchronized(objectLockRegistry) {
if(objectLockRegistry.size() > objectLockRegistryMaxSize) {
objectLockRegistry = new HashMap<>(); //brrrrrr...
}
}
In particular, 2 simultaneous calls to get a lock on a specific string might actually return 2 different instances of the same string, each stored in a different instance of your hashmap (unless they are interned), and you won't be locking on the same monitor.
You should either use an existing library or keep it a lot simpler.
If your question includes the keywords "optimize", "concurrent", and your solution includes a complicated locking scheme ... you're doing it wrong. It is possible to succeed at this sort of venture, but the odds are stacked against you. Prepare to diagnose bizarre concurrency bugs, including but not limited to, deadlock, livelock, cache incoherency... I can spot multiple unsafe practices in your example code.
Pretty much the only way to create a safe and effective concurrent algorithm without being a concurrency god is to take one of the pre-baked concurrent classes and adapt them to your need. It's just too hard to do unless you have an exceptionally convincing reason.
You might take a look at ConcurrentMap. You might also like CacheBuilder.
Using Threads and synchronize directly is covered by the beginning of most tutorials about multithreading and concurrency. However, many real-world examples require more sophisticated locking and concurrency schemes, which are cumbersome and error prone if you implement them yourself. To prevent reinventing the wheel over an over again, the Java concurrency library was created. There, you can find many classes that will be of great help to you. Try googling for tutorials about java concurrency and locks.
As an example for a lock which might help you, see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReadWriteLock.html .
Rather than roll your own cache I would take a look at Google's MapMaker. Something like this will give you a lock cache that automatically expires unused entries as they are garbage collected:
ConcurrentMap<String,String> objectLockRegistry = new MapMaker()
.softValues()
.makeComputingMap(new Function<String,String> {
public String apply(String s) {
return new String(s);
});
With this, the whole getObjectLock implementation is simply return objectLockRegistry.get(objectId) - the map takes care of all the "create if not already present" stuff for you in a safe way.
I Would do it similar, to you: just create a map of Object (new Object()).
But in difference to you i would use TreeMap<String, Object>
or HashMap
You call that the lockMap. One entry per file to lock. The lockMap is public available to all participating threads.
Each read and write to a specific file, gets the lock from the map. And uses syncrobize(lock) on that lock object.
If the lockMap is not fixed, and its content chan change, then reading and writing to the map must syncronized, too. (syncronized (this.lockMap) {....})
But your getObjectLock() is not safe, sync that all with your lock. (Double checked lockin is in Java not thread safe!) A recomended book: Doug Lea, Concurrent Programming in Java

Volatile HashMap vs ConcurrentHashMap

I have a cache class which contains a volatile HashMap<T> to store cache items.
I'm curious what would be the consequences of changing volatile HashMap to ConcurrentHashMap?
Would i gain performance increase? This cache is readonly cache.
What would be the best option to use? just HashMap? Cache is being populated on a interval.
First, it appears you don't understand what the volatile keyword does. It makes sure that if the reference value held by the variable declared volatile changes, other threads will see it rather than having a cached copy. It has nothing to do with thread-safety in regard to accessing the HashMap
Given that, and the fact that you say the HashMap is read-only ... you certainly don't need to use anything that provides thread-safety including a ConcurrentHashMap
Edit to add: Your last edit you now say "The cache is being populated on a interval"
That's not read-only then, is it?
If you're going to have threads reading from it while you are writing (updating the existing HashMap) then you should use a ConcurrentHashMap, yes.
If you are populating an entirely new HashMap then assigning it to the existing variable, then you use volatile
You say the cache is read-only, but also being updated on an interval which seems contradictory.
If the whole cache gets updated on an interval, I'd keep using the volatile.
The volatile will make sure that the updated map is safely published.
public final class Cache
{
private volatile Map<?,?> cache;
private void mapUpdate() {
Map<?,?> newCache = new HashMap<>();
// populate the map
// update the reference with an immutable collection
cache = Collections.unmodifiableMap(newCache);
}
}
If the interval update is modifying the same cache, then you probably want to use a ConcurrentHashMap, or copy the map, update the copy, and update the reference.
public final class Cache
{
private volatile Map<?,?> cache;
private void mapUpdate() {
Map<?,?> newCache = new HashMap<>(cache);
// update the map
// update the reference with an immutable collection
cache = Collections.unmodifiableMap(newCache);
}
}
I have a similar use case for my web application. I am using a HAshMap for my in-memory cache. The use case is as follows -
One user request comes in and first checks the cache for existence of a record using an input key. This is done in the add method.
If the object is not present then it inserts the new record in the cache.
Similarly in the remove method first checks the presence of a record in the cache using the key and if found just removes that.
I want to make sure of two threads are concurrently executing one on add and another on remove method will this approach make sure they at point of them they see the latest data in the cache? If i am not wrong then synchronized method takes care of thread safety where as volatile takes care of visibility.
private volatile HashMap<String,String> activeRequests = new HashMap<String,String>();
public synchronized boolean add(String pageKey, String space, String pageName) {
if (!(activeRequests.get(pageKey) == null)) {
return false;
}
activeRequests.put(pageKey, space + ":" + pageName);
return true;
}
public synchronized void remove(String pageKey) {
if(!(activeRequests.get(pageKey) == null))
activeRequests.remove(pageKey);
}
AFAIK, although the first answer explains correctly, depending on use case, using a volatile on a cache that is refreshed and replaced frequently is unnecessary overhead and can actually be bad or inconsistent assuming this is just static metadata snapshot and not updated by other threads.
If you take an example of a Http Request that reads everything from the cache to get everything needed, the request uses a reference of the map, then starts reading some keys from the reference, then half way while reading, cache reference is updated to a new hashmap (refresh), now it starts reading a different state of cache and can become inconsistent if entries in cache are not for a specific time snapshot T. With volatile, you read Key1:Val1 at T1, Key2:Val2 at T2 wheras you need Val1, Val2 to be read for the same snapshot at T1. With volatile your reference is always updated, you could read Key1:Val1 first time and Key1:Val2 second time giving different data in the same request.
Without volatile, the request will use a reference always pointing to a reference snapshot until it has completed processing. Without volatile, you will always read Key1:Val1 at T1 and same value Key2:Val1 at T2. Once all requests using this reference have completed, the older dereferenced map will be GCed.

ReentrantReadWriteLock - many readers at a time, one writer at a time?

I'm somewhat new to multithreaded environments and I'm trying to come up with the best solution for the following situation:
I read data from a database once daily in the morning, and stores the data in a HashMap in a Singleton object. I have a setter method that is called only when an intra-day DB change occurs (which will happen 0-2 times a day).
I also have a getter which returns an element in the map, and this method is called hundreds of times a day.
I'm worried about the case where the getter is called while I'm emptying and recreating the HashMap, thus trying to find an element in an empty/malformed list. If I make these methods synchronized, it prevents two readers from accessing the getter at the same time, which could be a performance bottleneck. I don't want to take too much of a performance hit since writes happen so infrequently. If I use a ReentrantReadWriteLock, will this force a queue on anyone calling the getter until the write lock is released? Does it allow multiple readers to access the getter at the same time? Will it enforce only one writer at a time?
Is coding this just a matter of...
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock read = readWriteLock.readLock();
private final Lock write = readWriteLock.writeLock();
public HashMap getter(String a) {
read.lock();
try {
return myStuff_.get(a);
} finally {
read.unlock();
}
}
public void setter()
{
write.lock();
try {
myStuff_ = // my logic
} finally {
write.unlock();
}
}
Another way to achieve this (without using locks) is the copy-on-write pattern. It works well when you do not write often. The idea is to copy and replace the field itself. It may look like the following:
private volatile Map<String,HashMap> myStuff_ = new HashMap<String,HashMap>();
public HashMap getter(String a) {
return myStuff_.get(a);
}
public synchronized void setter() {
// create a copy from the original
Map<String,HashMap> copy = new HashMap<String,HashMap>(myStuff_);
// populate the copy
// replace copy with the original
myStuff_ = copy;
}
With this, the readers are fully concurrent, and the only penalty they pay is a volatile read on myStuff_ (which is very little). The writers are synchronized to ensure mutual exclusion.
Yes, if the write lock is held by a thread then other threads accessing the getter method would block since they cannot acquire the read lock. So you are fine here. For more details please read the JavaDoc of ReentrantReadWriteLock - http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html
You're kicking this thing off at the start of the day... you'll update it 0-2 times a day and you're reading it 100s of times per day. Assuming that the reading is going to take, say 1 full second(a looonnnng time) in an 8 hour day(28800 seconds) you've still got a very low read load. Looking at the docs for ReentrantReadWriteLock you can 'tweek' the mode so that it will be "fair", which means the thread that's been waiting the longest will get the lock. So if you set it to be fair, I don't think that your write thread(s) are going to be starved.
References
ReentrantReadWriteLock

Categories

Resources