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;
}
}
testItmethod is not thread-safe. It's not enough thatcntsis protected by theRWLOCKduring modifications; it must also be protected during all reads, including theSystem.out.println(cnts)intestIt. That is, you need to lock, printcnts, then unlock.cntsafter all worker threads have terminated? Actually, it looks like that's the intent, but it could do it then, or after a timeout (if they haven't finished); the latter could be a problem.