Reputation: 1055
I'm looking for code equivalent to the following:
ConcurrentHashMap<int, Object> map = new ConcurrentHashMap<>();
map.computeIfAbsent(key, n -> f(n));
Where f(n)
is HTTP network call and blocking for the result
Bur referring to single element held in AtomicReference<Object>
where I need to ensure f
is called only once upon even if multiple threads do the access concurrently.
I tried using compareAndSet
but this doesn't allow lambda passing.
Does updateAndGet
achieve that? Its documentation mentions
The function should be side-effect-free, since it may be re-applied when attempted updates fail due to contention among threads.
Which doesn't seem to fill the need of invoking f
only once.
Upvotes: 1
Views: 455
Reputation: 328
I believe you need something like a concurrent lazy initializer. It is possible to achieve this using:
If your requirement is to have only 1 instance in an application, you can use a thread-safe singleton. https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
public class Something {
private final Result result;
private Something() {
result = f();
}
private static class LazyHolder {
public static final Something INSTANCE = new Something();
}
public static Something getInstance() {
return LazyHolder.INSTANCE;
}
}
If you want to have it in different places of your application, you can use:
Apache Commons Lang ConcurrentInitializer like LazyInitializer:
ConcurrentInitializer<> lazyInitializer = new LazyInitializer<Result>() {
@Override
protected Foo initialize() throws ConcurrentException {
return f();
}
};
Get instance
Result instance = lazyInitializer.get();
Google's Guava link:
Supplier<Result> resultSupplier = Suppliers.memoize(new Supplier<Result>() {
public Result get() {
return f();
}
});
Yon can create your own concurrent lazy initalizer in lock-free manner.
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
public class LazyConcurrentSupplier<T> implements Supplier<T> {
static class Container<T> {
public static final int NULL_PHASE = -1, CREATING_PHASE = 0, CREATED = 1;
final int i;
final T value;
public Container(int i, T value) {
this.i = i;
this.value = value;
}
}
private final Container<T> NULL = new Container<>(Container.NULL_PHASE, null),
CREATING = new Container<>(Container.CREATING_PHASE, null);
private final AtomicReference<Container<T>> ref = new AtomicReference<>(NULL);
private final Supplier<T> supplier;
public LazyConcurrentSupplier(Supplier<T> supplier) {
this.supplier = supplier;
}
@Override
public T get() {
Container<T> prev;
do {
if (ref.compareAndSet(NULL, CREATING)) {
T res = supplier.get();
ref.set(new Container<>(Container.CREATED, res));
return res;
} else {
prev = ref.get();
if (prev.i == Container.CREATED) {
return prev.value;
}
}
} while (prev.i < Container.CREATED);
return prev.value;
}
}
Upvotes: 1
Reputation: 936
You could use an AtomicBoolean
with an initial value of true
and allow each thread should call AtomicBoolean::getAndSet
with the value false
. If the return value is true
then you execute your function.
This will ensure that the call is only made once since only the first thread will succeed.
Upvotes: 0
Reputation: 149
From your question, I think you want to avoid doing the HTTP request multiple times.
You could have a map of FutureTask(s) that asynchronously performs the HTTP request for you. In this way, if a thread tries to computeIfAbsent it will see the FutureTask created by another thread even if the HTTP operation is not done yet.
Upvotes: 0