Reputation: 65851
I posted an answer here where the code demonstrating use of the putIfAbsent
method of ConcurrentMap
read:
ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong> ();
public long addTo(String key, long value) {
// The final value it became.
long result = value;
// Make a new one to put in the map.
AtomicLong newValue = new AtomicLong(value);
// Insert my new one or get me the old one.
AtomicLong oldValue = map.putIfAbsent(key, newValue);
// Was it already there? Note the deliberate use of '!='.
if ( oldValue != newValue ) {
// Update it.
result = oldValue.addAndGet(value);
}
return result;
}
The main downside of this approach is that you have to create a new object to put into the map whether it will be used or not. This can have significant effect if the object is heavy.
It occurred to me that this would be an opportunity to use Lambdas. I have not downloaded Java 8 n'or will I be able to until it is official (company policy) so I cannot test this but would something like this be valid and effective?
public long addTo(String key, long value) {
return map.putIfAbsent( key, () -> new AtomicLong(0) ).addAndGet(value);
}
I am hoping to use the lambda to delay the evaluation of the new AtomicLong(0)
until it is actually determined that it should be created because it does not exist in the map.
As you can see this is much more succinct and functional.
Essentially I suppose my questions are:
Upvotes: 7
Views: 5845
Reputation: 100269
Note that using Java 8 ConcurrentHashMap
it's completely unnecessary to have AtomicLong
values. You can safely use ConcurrentHashMap.merge:
ConcurrentMap<String, Long> map = new ConcurrentHashMap<String, Long>();
public long addTo(String key, long value) {
return map.merge(key, value, Long::sum);
}
It's much simpler and also significantly faster.
Upvotes: 2
Reputation: 132460
UPDATE 2015-08-01
The computeIfAbsent
method as described below has indeed been added to Java SE 8. The semantics appear to be very close to the pre-release version.
In addition, computeIfAbsent
, along with a whole pile of new default methods, has been added to the Map
interface. Of course, maps in general can't support atomic updates, but the new methods add considerable convenience to the API.
What you're trying to do is quite reasonable, but unfortunately it doesn't work with the current version of ConcurrentMap
. An enhancement is on the way, however. The new version of the concurrency library includes ConcurrentHashMapV8
which contains a new method computeIfAbsent
. This pretty much allows you to do exactly what you're looking to do. Using this new method, your example could be rewritten as follows:
public long addTo(String key, long value) {
return map.computeIfAbsent( key, () -> new AtomicLong(0) ).addAndGet(value);
}
For further information about the ConcurrentHashMapV8
, see Doug Lea's initial announcement thread on the concurrency-interest mailing list. Several messages down the thread is a followup message that shows an example very similar to what you're trying to do. (Note however the old lambda syntax. That message was from August 2011 after all.) And here is recent javadoc for ConcurrentHashMapV8
.
This work is intended to be integrated into Java 8, but it hasn't yet as far as I can see. Also, this is still a work in progress, names and specs may change, etc.
Upvotes: 8
Reputation: 14035
Unfortunately it's not as easy as that. There are two main problems with the approach you've sketched out:
1. The type of the map would need to change from Map<String, AtomicLong>
to Map<String, AtomicLongFunction>
(where AtomicLongFunction
is some function interface that has a single method that takes no arguments and returns an AtomicLong
).
2. When you retrieve the element from the map you'd need to apply the function each time to get the AtomicLong
out of it. This would result in creating a new instance each time you retrieve it, which is not likely what you wanted.
The idea of having a map that runs a function on demand to fill up missing values is a good one, though, and in fact Google's Guava library has a map that does exactly that; see their MapMaker. In fact that code would benefit from Java 8 lambda expressions: instead of
ConcurrentMap<Key, Graph> graphs = new MapMaker()
.concurrencyLevel(4)
.weakKeys()
.makeComputingMap(
new Function<Key, Graph>() {
public Graph apply(Key key) {
return createExpensiveGraph(key);
}
});
you'd be able to write
ConcurrentMap<Key, Graph> graphs = new MapMaker()
.concurrencyLevel(4)
.weakKeys()
.makeComputingMap((Key key) -> createExpensiveGraph(key));
or
ConcurrentMap<Key, Graph> graphs = new MapMaker()
.concurrencyLevel(4)
.weakKeys()
.makeComputingMap(this::createExpensiveGraph);
Upvotes: 2
Reputation: 1073
AtomicLong
is not really a heavy object. For heavier objects I would consider a lazy proxy and provide a lambda to that one to create the object if needed.
class MyObject{
void doSomething(){}
}
class MyLazyObject extends MyObject{
Funktion create;
MyLazyObject(Funktion create){
this.create = create;
}
MyObject instance;
MyObject getInstance(){
if(instance == null)
instance = create.apply();
return instance;
}
@Override void doSomething(){getInstance().doSomething();}
}
public long addTo(String key, long value) {
return map.putIfAbsent( key, new MyLazyObject( () -> new MyObject(0) ) );
}
Upvotes: 2