I am trying to implement lock by which I want to avoid reads from happening whenever I am doing a write on my three maps. So my requirement is -
As I have three Maps - primaryMapping
, secondaryMapping
and tertiaryMapping
so it should return either all the new values of three updated maps or it should return all the old values of the map. Basically, while updating I don't want to return primaryMapping
having old values, secondaryMapping
having having new values, and tertiaryMapping
with new values.
It should be consistent, either it should return old values or it should return new values after updating the maps. In my case, updating of maps will happen once in three months or four months.
Below is my ClientData
class in which I am using ReentrantLock
in which the whole logic is there -
public class ClientData {
private static final class MapContainer {
private Map<String, Map<Integer, String>> value = null;
public Map<String, Map<Integer, String>> getValue() {
return value;
}
public void setValue(Map<String, Map<Integer, String>> value) {
this.value = value;
}
}
private static final MapContainer primaryMapping = new MapContainer();
private static final MapContainer secondaryMapping = new MapContainer();
private static final MapContainer tertiaryMapping = new MapContainer();
private static final MapContainer[] containers = {primaryMapping, secondaryMapping, tertiaryMapping};
private static boolean allset = false;
private static final Lock lock = new ReentrantLock();
private static final Condition allsetnow = lock.newCondition();
private static final Map<String, Map<Integer, String>> getMapping(MapContainer container) {
lock.lock();
try {
while (!allset) {
allsetnow.await();
}
return container.getValue();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // reset interruptedd state.
throw new IllegalStateException(e);
} finally {
lock.unlock();
}
}
public static void setAllMappings(Map<String, Map<Integer, String>> primary,
Map<String, Map<Integer, String>> secondary,
Map<String, Map<Integer, String>> tertiary) {
lock.lock();
try{
// how to avoid this?
if (allset) {
throw new IllegalStateException("All the maps are already set");
}
primaryMapping.setValue(primary);
secondaryMapping.setValue(secondary);
tertiaryMapping.setValue(tertiary);
allset = true;
allsetnow.signalAll();
} finally {
lock.unlock();
}
}
public static Map<String, Map<Integer, String>> getPrimaryMapping() {
return getMapping(primaryMapping);
}
public static Map<String, Map<Integer, String>> getSecondaryMapping() {
return getMapping(secondaryMapping);
}
public static Map<String, Map<Integer, String>> getTertiaryMapping() {
return getMapping(tertiaryMapping);
}
}
And below is my background thread code which will get the data from my service URL and it keeps on running every 10 minutes once my application has started up, and then it will parse the data coming from the url and store it in a ClientData
class variable in those three maps.
public class TempScheduler {
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public void startScheduler() {
final ScheduledFuture<?> taskHandle = scheduler.scheduleAtFixedRate(new Runnable() {
public void run() {
try {
callServers();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}, 0, 10, TimeUnit.MINUTES);
}
}
// call the servers and get the data and then parse
// the response.
private void callServers() {
String url = "url";
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject(url, String.class);
parseResponse(response);
}
// parse the response and store it in a variable
private void parseResponse(String response) {
//...
ConcurrentHashMap<String, Map<Integer, String>> primaryTables = null;
ConcurrentHashMap<String, Map<Integer, String>> secondaryTables = null;
ConcurrentHashMap<String, Map<Integer, String>> tertiaryTables = null;
//...
// store the data in ClientData class variables if anything has changed
// which can be used by other threads
if(changed) {
ClientData.setAllMappings(primaryTables, secondaryTables, tertiaryTables);
}
}
}
I will be using getPrimaryMapping
, getSecondaryMapping
and getTertiaryMapping
in ClientData
class from the main thread in my main appliction so I want to return either all the new set of values from these three maps or if update is happening, then either block it and return all the new set of values for those three maps after the updates are done.
Problem Statement:-
In my code base as shown above in ClientData
class, I guess, I won't be able to update the maps once it has been set for the first time as this line will cause a problem and it will throw a exception and then also how to implement my second point as shown above?
// how to avoid this?
if (allset) {
throw new IllegalStateException("All the maps are already set");
}
How can I successfully implement all my above two points? I guess, there is something very minor thing which I am missing here? I want to use ReentrantLock here but any other suggestions are also welcome. My main concern is the performance issues. Because I will be doing set on those three maps once in a three month so this is fine. But get on three maps will be happening from main application code every 1000 requests per second so I want to be pretty fast.
Initially, I was thinking to remove this if statement -
// how to avoid this?
if (allset) {
throw new IllegalStateException("All the maps are already set");
}
But I am doubting, it will not work since then there will be mismatched of maps between the threads while I am updating the maps?
And this is the way, I am reading the values from ClientData class
from the main application thread -
String data1 = ClientData.getPrimaryMapping().get(some_value1).get(some_value2);
String data2 = ClientData.getSecondaryMapping().get(some_value1).get(some_value3);
String data3 = ClientData.getTertiaryMapping().get(some_value1).get(some_value4);
Update:-
Another solution using CountDownLatch
which fulfills all the above conditions -
Below is my ClientData
class in which I am using CountDownLatch
-
public class ClientData {
public static class Mappings {
public final Map<String, Map<Integer, String>> primary;
public final Map<String, Map<Integer, String>> secondary;
public final Map<String, Map<Integer, String>> tertiary;
public Mappings(
Map<String, Map<Integer, String>> primary,
Map<String, Map<Integer, String>> secondary,
Map<String, Map<Integer, String>> tertiary
) {
this.primary = primary;
this.secondary = secondary;
this.tertiary = tertiary;
}
}
private static final AtomicReference<Mappings> mappings = new AtomicReference<>();
private static final CountDownLatch hasBeenInitialized = new CountDownLatch(1);
public static Mappings getMappings() {
try {
hasBeenInitialized.await();
return mappings.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException(e);
}
}
public static void setMappings(
Map<String, Map<Integer, String>> primary,
Map<String, Map<Integer, String>> secondary,
Map<String, Map<Integer, String>> tertiary
) {
setMappings(new Mappings(primary, secondary, tertiary));
}
public static void setMappings(Mappings newMappings) {
mappings.set(newMappings);
hasBeenInitialized.countDown();
}
}
I will be using ClientData
class in main application thread like this -
Mappings mappings = ClientData.getMappings();
// use mappings.primary
// use mappings.secondary
// use mappings.tertiary
Does this code has any performance impact? In short which one will be better, should I go with ReentrantReadWriteLock
or the above CountDownLatch
one solution?
So now the questions is, CountDownLatch
solution vs ReentrantReadWriteLock
solution? Which one will perform better in high read / low write use case?
Can anyone also provide an example which uses ReentrantReadWriteLock
basis on my above solution if possible? By that, I will be able to compare performance against CountDownLatch
solution which I have earlier vs ReentrantReadWriteLock
one.
As I am not able to come up with a solution which uses ReentrantReadWriteLock
yet for my above use case.
NOTE:-
In my case, writes will be happening once in three or four month. But reads are going to happen from multiple threads at a very high speed, 1000
requests per second. So it has to be pretty fast.
You should continue your restructuring along the line you're going with the introduction of Mappings
. You don't need to manage the three references individually (this is where it gets complex). Instead, manage one reference.
class ClientData {
// This is immutable after creation (like your OP)
class Mappings { /* definition from your original post */ }
// this should be volatile;
private static volatile Mappings instance;
// the read path proceeds without acquiring any lock at all. Hard to
// get faster than a volatile read. Note the double-checked locking pattern
// works JDK 6 or greater when using volatile references (above)
public static Mappings getMappings() {
Mappings result = instance;
if(result == null) {
synchronized(ClientData.class) {
result = instance;
// recall while() is required to handle spurious wakeup
while(result == null) {
ClientData.class.wait();
result = instance;
}
}
}
}
public static setMappings(Map one, Map two, Map three) {
synchronized(ClientData.class) {
instance = new Mappings(one,two,three);
ClientData.class.notifyAll()
}
}
}
I think this has the following benefits:
It's somewhat unfortunate (IMHO) that java doesn't have a good "Waitable Reference" built in. But one can't ask for everything! Third party libraries have some support -- Guava's Suppliers.memoize()
is a good place to start.
Good luck with your project.