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.
Related
Is it safe to use getParameter
Since I can tolerate the value is not latest.
And when next time I can get the latest value of Parameter
Code like this :
public class ParameterManager {
private volatile Map<String, Parameter> scenarioParameterMap = Maps.newHashMap();
public ParameterManager(String appName) throws DarwinClientException {
}
public Parameter getParameter(String scenario) {
return scenarioParameterMap.get(scenario);
}
public void update(String scenario, Map<String, String> parameters) {
if (scenarioParameterMap.containsKey(scenario)) {
Parameter parameter = scenarioParameterMap.get(scenario);
parameter.update(parameters);
} else {
scenarioParameterMap.put(scenario, new Parameter(scenario, parameters));
}
}
}
or the update is just use
scenarioParameterMap.put(scenario, new Parameter(scenario, parameters));
volatile does not help here at all. It only protects the reference held in scenarioParameterMap, not the contents of that map. Since you're not reassigning it to point to a different map at any point, volatile is extraneous.
This code is not threadsafe. You need to use proper synchronization, be that via synchronized, or using a concurrent map, or other equivalent method.
Since I can tolerate the value is not latest.
Thread non-safety can be more dangerous than that. It could give you wrong results. It could crash. You can't get by thinking that the worst case is stale data. That's not the case.
Imagine that Map.put() is in the middle of updating the map and has the internal data in some temporarily invalid state. If Map.get() runs at the same time who knows what might go wrong. Sometimes adding an entry to a hash map will cause the whole thing to be reallocated and re-bucketed. Another thread reading the map at that time would be very confused.
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);
}
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();
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".