I think what you are looking for is a Multiton
:
/**
* Holds a thread-safe map of unique create-once items.
*
* Contract:
*
* Only one object will be made for each key presented.
*
* Thread safe.
*
* @author OldCurmudgeon
* @param <K>
* @param <V>
*/
public class Multiton<K, V> {
// Map from the key to the futures of the items.
private final ConcurrentMap<K, Future<V>> multitons = new ConcurrentHashMap<>();
// The creator can create an item of type V.
private final Creator<K, V> creator;
public Multiton(Creator<K, V> creator) {
this.creator = creator;
}
/**
* There can be only one.
*
* Use a FutureTask to do the creation to ensure only one construction.
*
* @param key
* @return
* @throws InterruptedException
* @throws ExecutionException
*/
public V get(final K key) throws InterruptedException, ExecutionException {
// Already made?
Future<V> f = multitons.get(key);
if (f == null) {
// Plan the future but do not create as yet.
FutureTask<V> ft = new FutureTask<>(() -> creator.create(key));
// Store it.
f = multitons.putIfAbsent(key, ft);
if (f == null) {
// It was successfully stored - it is the first (and only)
f = ft;
// Make it happen.
ft.run();
}
}
// Wait for it to finish construction and return the constructed.
return f.get();
}
/**
* Returns a Map indicating the current state.
*
* @return a Map which should reflect the current state.
*
* @throws java.lang.InterruptedException
* @throws java.util.concurrent.ExecutionException
*/
public Map<K, V> getMap() throws InterruptedException, ExecutionException {
Map<K, V> map = new HashMap<>();
for (Map.Entry<K, Future<V>> e : multitons.entrySet()) {
map.put(e.getKey(), e.getValue().get());
}
return map;
}
/**
* User provides one of these to do the construction.
*
* @param <K>
* @param <V>
*/
public abstract static class Creator<K, V> {
// Return a new item under the key.
abstract V create(K key) throws ExecutionException;
}
}
Usage - for demonstration - adds up all integers up to 999, keying on their first digit:
Multiton<String, AtomicInteger> counts = new Multiton<>(
new Creator<String, AtomicInteger>() {
@Override
AtomicInteger create(String key) throws ExecutionException {
return new AtomicInteger();
}
}
);
public void test() throws InterruptedException, ExecutionException {
for (int i = 0; i < 1000; i++) {
counts.get(Integer.toString(i).substring(0, 1)).addAndGet(i);
}
System.out.println(counts.getMap());
}
Prints:
{0=0, 1=15096, 2=25197, 3=35298, 4=45399, 5=55500, 6=65601, 7=75702, 8=85803, 9=95904}
Java < 8 version:
/**
* Holds a thread-safe map of unique create-once items.
*
* Contract:
*
* Only one object will be made for each key presented.
*
* Thread safe.
*
* @author OldCurmudgeon
* @param <K>
* @param <V>
*/
public class Multiton<K, V> {
// Map from the key to the futures of the items.
private final ConcurrentMap<K, Future<V>> multitons = new ConcurrentHashMap<>();
// The creator can create an item of type V.
private final Creator<K, V> creator;
public Multiton(Creator<K, V> creator) {
this.creator = creator;
}
/**
* There can be only one.
*
* Use a FutureTask to do the creation to ensure only one construction.
*
* @param key
* @return
* @throws InterruptedException
* @throws ExecutionException
*/
public V get(final K key) throws InterruptedException, ExecutionException {
// Already made?
Future<V> f = multitons.get(key);
if (f == null) {
// Plan the future but do not create as yet.
FutureTask<V> ft = new FutureTask<>(new Callable<V>() {
@Override
public V call() throws Exception {
// Doing this inline may be a little contrived but it maintains the linkage with the Java-8 version.
return creator.create(key);
}
}
);
// Store it.
f = multitons.putIfAbsent(key, ft);
if (f == null) {
// It was successfully stored - it is the first (and only)
f = ft;
// Make it happen.
ft.run();
}
}
// Wait for it to finish construction and return the constructed.
return f.get();
}
/**
* Returns a Map indicating the current state.
*
* @return a Map which should reflect the current state.
*
* @throws java.lang.InterruptedException
* @throws java.util.concurrent.ExecutionException
*/
public Map<K, V> getMap() throws InterruptedException, ExecutionException {
Map<K, V> map = new HashMap<>();
for (Map.Entry<K, Future<V>> e : multitons.entrySet()) {
map.put(e.getKey(), e.getValue().get());
}
return map;
}
/**
* User provides one of these to do the construction.
*
* @param <K>
* @param <V>
*/
public abstract static class Creator<K, V> {
// Return a new item under the key.
abstract V create(K key) throws ExecutionException;
}
}