Is there any better way to cache up some very large objects, that can only be created once, and therefore need to be cached ? Currently, I have the following:
public enum LargeObjectCache {
INSTANCE;
private Map<String, LargeObject> map = new HashMap<...>();
public LargeObject get(String s) {
if (!map.containsKey(s)) {
map.put(s, new LargeObject(s));
}
return map.get(s);
}
}
There are several classes that can use the LargeObjects, which is why I decided to use a singleton for the cache, instead of passing LargeObjects to every class that uses it.
Also, the map doesn't contain many keys (one or two, but the key can vary in different runs of the program) so, is there another, more efficient map to use in this case ?
You may need thread-safety to ensure you don't have two instance of the same name.
It does matter much for small maps but you can avoid one call which can make it faster.
public LargeObject get(String s) {
synchronized(map) {
LargeObject ret = map.get(s);
if (ret == null)
map.put(s, ret = new LargeObject(s));
return ret;
}
}
As it has been pointed out, you need to address thread-safety. Simply using Collections.synchronizedMap() doesn't make it completely correct, as the code entails compound operations. Synchronizing the entire block is one solution. However, using ConcurrentHashMap will result in a much more concurrent and scalable behavior if it is critical.
public enum LargeObjectCache {
INSTANCE;
private final ConcurrentMap<String, LargeObject> map = new ConcurrentHashMap<...>();
public LargeObject get(String s) {
LargeObject value = map.get(s);
if (value == null) {
value = new LargeObject(s);
LargeObject old = map.putIfAbsent(s, value);
if (old != null) {
value = old;
}
}
return value;
}
}
You'll need to use it exactly in this form to have the correct and the most efficient behavior.
If you must ensure only one thread gets to even instantiate the value for a given key, then it becomes necessary to turn to something like the computing map in Google Collections or the memoizer example in Brian Goetz's book "Java Concurrency in Practice".
Related
I'm trying to create a method with a ConcurrentHashMap with the following behavior.
Read no lock
Write lock
prior to writing,
read to see if record exist,
if it still doesn't exist, save to database and add record to map.
if record exist from previous write, just return record.
My thoughts.
private Object lock1 = new Object();
private ConcurrentHashMap<String, Object> productMap;
private Object getProductMap(String name) {
if (productMap.isEmpty()) {
productMap = new ConcurrentHashMap<>();
}
if (productMap.containsKey(name)) {
return productMap.get(name);
}
synchronized (lock1) {
if (productMap.containsKey(name)) {
return productMap.get(name);
} else {
Product product = new Product(name);
session.save(product);
productMap.putIfAbsent(name, product);
}
}
}
Could someone help me to understand if this is a correct approach?
There are several bugs here.
If productMap isn't guaranteed to be initialized, you will get an NPE in your first statement to this method.
The method isn't guaranteed to return anything if the map is empty.
The method doesn't return on all paths.
The method is both poorly named and unnecessary; you're trying to emulate putIfAbsent which half accomplishes your goal.
You also don't need to do any synchronization; ConcurrentHashMap is thread safe for your purposes.
If I were to rewrite this, I'd do a few things differently:
Eagerly instantiate the ConcurrentHashMap
Bind it to ConcurrentMap instead of the concrete class (so ConcurrentMap<String, Product> productMap = new ConcurrentHashMap<>();)
Rename the method to putIfMissing and delegate to putIfAbsent, with some logic to return the same record I want to add if the result is null. The above absolutely depends on Product having a well-defined equals and hashCode method, such that new Product(name) will produce objects with the same values for equals and hashCode if provided the same name.
Use an Optional to avoid any NPEs with the result of putIfAbsent, and to provide easier to digest code.
A snippet of the above:
public Product putIfMissing(String key) {
Product product = new Product(key);
Optional<Product> result =
Optional.ofNullable(productMap.putIfAbsent(key, product));
session.save(result.orElse(product));
return result.orElse(product);
}
Basically, what is needed is to synchronize requests to each of the records.
Some of the codes I can think of is like this:
//member variable
ConcurrentHashMap<Long, Object> lockMap = new ConcurrentHashMap<Long, Object>();
//one method
private void maintainLockObjects(long id){
lockMap.putIfAbsent(id, new Object());
}
//the request method
bar(long id){
maintainLockObjects(id);
synchronized(lockMap.get(id)){
//logic here
}
}
Have a look at ClassLoader.getClassLoadingLock:
Returns the lock object for class loading operations. For backward compatibility, the default implementation of this method behaves as follows. If this ClassLoader object is registered as parallel capable, the method returns a dedicated object associated with the specified class name. Otherwise, the method returns this ClassLoader object.
Its implementation code may look familiar to you:
protected Object getClassLoadingLock(String className) {
Object lock = this;
if (parallelLockMap != null) {
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}
The first null check is only for the mentioned backwards compatibility. So besides that, the only difference between this heavily used code and your approach is that this code avoids to call get afterwards as putIfAbsent already returns the old object if there is one.
So the simply answer, it works and this pattern also proving within a really crucial part of Oracle’s JRE implementation.
I've crawl through many question regarding this area but my question still remains with me. I'm seeking some elaborate answer as well(If you kind enough?). So i could understand this more clearly and community as well.
This is my question. I have this map.
private static volatile Map<Integer, Type> types;
and have static getter as,
static Type getType(final int id)
{
if (types == null)
{
synchronized (CLASSNAME.class)
{
if (types == null)
{
types = new HashMap<Integer, Type>();
....add items to the map
}
}
}
return types.get(id);
}
Problem in this code is first thread can initialize the types so it won't be null anymore. While first thread adding values to map second thread can retrieve data from it. That means corrupted data.
I see that this can be avoid by synchronizing whole method but then multiple readers is not possible. It's an one time construction for that map and there will be no modification. So multiple readers is essential.
Also we can use Collections.synchronizeMap but if i'm correct it also not allowing concurrent readers. I tried but ConcurrentHashMap doesn't solve this either. Maybe due to it's independent partition locking behavior.
Simply what i need is no reading until map created fully and then multiple read should be possible.
Anyone got a solution?
Thanks.
There is a simple solution to your problem. Use a temporary variable, so that the reference types is null as long as the map is not completely populated. If you change the code in that way, it is thread-safe and quite efficient.
static Type getType(final int id) {
if (types == null) {
synchronized (CLASSNAME.class) {
if (types == null) {
HashMap<Integer, Type> temp = new HashMap<>();
// populate temp
types = temp;
}
}
}
return types.get(id);
}
Thread-safe, lazy and efficient initialization is a frequently required feature. Unfortunately, it's not directly supported by Java, neither by the programming language nor by the standard library. Instead, there are different patterns, and your implementation is known as Double-checked locking.
A short excursion to C++: C++11 has support for lazy, thread-safe initialization both in the language and in the library. If there is only one global type mapping, you can write the following in C++:
auto populated_map()
{
std::map<int, type> result;
// ... populate map
return result;
}
auto get_type(int id) -> const type&
{
static const std::map<int, type> map = populated_map();
return map.find(id)->second;
}
If you need lazy initialization per object, you can use the library support around std::once_flag and std::call_once:
class types
{
private:
std::once_flag _flag;
std::map<int, type> _map;
public:
auto get_type(int id) -> const type&
{
std::call_once(_flag, [this] { _map = populated_map(); });
return _map.find(id)->second;
}
};
Take a look into the Memoization pattern. There are specific implementations available in Java 8 but if you aren't adopting that soon, look at Guava's MapMaker, specifically:
private final ConcurrentMap<Map<Integer, Type> types = new MapMaker()
.makeComputingMap(new Function<Integer, Type>() {
public Graph apply(Type key) {
return loadForType(key);
}
});
In this case, no one thread will be populating this map (it may be that a single thread does). The idea is, when a thread enters it will check to see if a value for any Integer is available. If not it will run the function once, if it is, it will return it while not blocking
If I look at ConcurrentHashMap at java, and specifically the putIfAbsent method, a typical usage of this method would be:
ConcurrentMap<String,Person> map = new ConcurrentHashMap<>();
map.putIfAbsent("John",new Person("John"));
The problem is that the Person object is always initialized.
Is there some helper collection (maybe some java framework providing this)
that will give me similar behavior of ConcurrentHashMap, and that will work with a functor or any other mean to construct the value object,
and the construction code (i.e - functor.execute() ) will be called only if the map does not contain a value for the given key?
The only way to do this is to use locking. You can minimise the impact of this by using checking first.
if(!map.containsKey("John"))
synchronized(map) {
if(!map.containsKey("John"))
map.put("John", new Person("John"));
}
The reson you need locking is that you need to hold the map while you create the Person to prevent other threads trying to add the same object at the same time. ConcurrentMap doesn't support blocking operations like this directly.
If you need to minise locking to a specific key you can do the following.
ConcurrentMap<String, AtomicReference<Person>> map = new ConcurrentHashMap<String, AtomicReference<Person>>();
String name = "John";
AtomicReference<Person> personRef = map.get(name);
if (personRef == null)
map.putIfAbsent(name, new AtomicReference<Person>());
personRef = map.get(name);
if (personRef.get() == null)
synchronized (personRef) {
if (personRef.get() == null)
// can take a long time without blocking use of other keys.
personRef.set(new Person(name));
}
Person person = personRef.get();
I'd like to be able to conditionally replace a value in a ConcurrentHashMap. That is, given:
public class PriceTick {
final String instrumentId;
...
final long timestamp;
...
And a class (let's call it TickHolder) which owns a ConcurrentHashMap (let's just call it map).
I wish to be able to implement a conditional put method, so that if there's no entry for the key, the new one is inserted, but if there is an existing entry, the new one is inserted only if the timestamp value in the new PriceTick is greater than the existing one.
For an old-school HashMap solution, TickHolder would have a put method:
public void add(PriceTick tick) {
synchronized(map) {
if ((map.get(tick.instrumentId) == null)
|| (tick.getTimestamp() > map.get(tick.instrumentId).getTimestamp()) )
map.put(tick.instrumentId, tick);
}
}
With a ConcurrentHashMap, one would want to drop the synchronization and use some atomic method like replace, but that's unconditional. So clearly the "conditional replace" method must be written.
However, since the test-and-replace operation is non-atomic, in order to be thread safe, it would have to be synchronized - but my initial reading of the ConcurrentHashMap source leads me to think that external synchronization and their internal locks will not work very well, so at a very minimum, every Map method which performs structural changes and the containing class performs would have to be synchronized by the containing class... and even then, I'm going to be fairly uneasy.
I thought about subclassing ConcurrentHashMap, but that seems to be impossible. It makes use of an inner final class HashEntry with default access, so although ConcurrentHashMap is not final, it's not extensible.
Which seems to mean that I have to fall back to implementing TickHolder as containing an old-school HashMap in order to write my conditional replace method.
So, the questions: am I right about the above? Have I (hopefully) missed something, whether obvious or subtle, which would lead to a different conclusion? I'd really like to be able to make use of that lovely striped locking mechanism here.
The non-deterministic solution is to loop replace():
do {
PriceTick oldTick = map.get(newTick.getInstrumentId());
} while ((oldTick == null || oldTick.before(newTick)) && !map.replace(newTick.getInstrumentId(), oldTick, newTick);
Odd though it may seem, that is a commonly suggested pattern for this kind of thing.
#cletus solution formed the base for my solution to an almost identical problem. I think a couple of changes are needed though as if oldTick is null then replace throws a NullPointerException as stated by #hotzen
PriceTick oldTick;
do {
oldTick = map.putIfAbsent(newTick.getInstrumentId());
} while (oldTick != null && oldTick.before(newTick) && !map.replace(newTick.getInstrumentId(), oldTick, newTick);
The correct answer should be
PriceTick oldTick;
do {
oldTick = map.putIfAbsent(newTick.getInstrumentId(), newTick);
if (oldTick == null) {
break;
}
} while (oldTick.before(newTick) && !map.replace(newTick.getInstrumentId(), oldTick, newTick);
As an alternative, could you create a TickHolder class, and use that as the value in your map? It makes the map slightly more cumbersome to use (getting a value is now map.getValue(key).getTick()), but it lets you keep the ConcurrentHashMap's behavior.
public class TickHolder {
public PriceTick getTick() { /* returns current value */
public synchronized PriceTick replaceIfNewer (PriceTick pCandidate) { /* does your check */ }
}
And your put method becomes something like:
public void updateTick (PriceTick pTick) {
TickHolder value = map.getValue(pTick.getInstrumentId());
if (value != null) {
TickHolder newTick = new TickHolder(pTick);
value = map.putIfAbsent(pTick.getInstrumentId(), newTick);
if (value == null) {
value = newTick;
}
}
value.replaceIfNewer(pTick);
}