Hanna Khalil
Hanna Khalil

Reputation: 1055

Java ConcurrentHashMap#computeIfAbsent equivalent for AtomicReference

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

Answers (3)

Yan
Yan

Reputation: 328

I believe you need something like a concurrent lazy initializer. It is possible to achieve this using:

  1. 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;
          }
    }
    
  2. 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();
    }
});
  1. 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

Eric
Eric

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

Roberto Fronteddu
Roberto Fronteddu

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

Related Questions