2

I'd like to implement a call like:

public Response getContent() throws MyException

This method internally calls to a web service, but I only want one request to the web service at the same time. That is: when the first request comes, it goes to the web service. If in the meanwhile another requests arrive, it wait for the result of the first request. After the first request returns (or throws exception), all the pending requests return too (or throw exceptions). From this point, if another request comes, it goes again to the web service with the same criteria (only one web service call at the same time)

What is the best approach for implementing this in Java/Android?

Thanks

Addev
  • 31,819
  • 51
  • 183
  • 302
  • 1
    Producer/consumer, could be one – Blackbelt Apr 12 '18 at 09:38
  • You could also avoid the latency and deliver to each requestor the cached value, while continuing to fetch a new one in the background. But if you insist on blocking, you can use a `CompletableFuture`. – Marko Topolnik Apr 12 '18 at 09:39
  • how about using synchronization? – Pavan Apr 12 '18 at 09:47
  • 2
    questions like "what is the best X" are heavily subjective and attract opinion based answers. We do not want those, please refrain from asking such questions – Tim Apr 12 '18 at 09:50
  • There is no "best". There is only "good for this reason". Any approach that works is good enough, but then you need a lot more criteria to select from set of possible solutions. – M. Prokhorov Apr 12 '18 at 09:54

2 Answers2

4

Here's a solution with a CompletableFuture that always blocks waiting for the new result. If you call get() and there is an existing result, it triggers a new fetch and blocks. If a fetch is already in progress, it joins other threads awaiting that result.

import java.util.concurrent.CompletableFuture
import java.util.concurrent.atomic.AtomicReference

class OneAtATime<out T> {
    private val pending = AtomicReference(CompletableFuture<T>())
    init {
        startFetching(pending.get())
    }

    fun get(): T {
        val current = pending.get()
        if (!current.isDone) {
            return current.get()
        }
        val next = CompletableFuture<T>()
        return if (pending.compareAndSet(current, next)) {
            startFetching(next)
            next.get()
        } else {
            pending.get().get()
        }
    }

    private fun startFetching(future: CompletableFuture<T>) {
        TODO("Implementation must call future.complete(newContent)")
    }
}
Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
1

I would recommend starting to learn how a blocking queue works:
BlockingQueue

After that you can start investigating solution how to have one call at a time. One solution is to use a semaphore:
Blocking queue with a semaphore

(from the link above) snippet that applies to your case:
EDIT

 public class CustomBlockingQueue {
  private List<Object> queue = new LinkedList<Object>();
  private int limit;
  private Semaphore mutex; // for the critical section
  public CustomBlockingQueue() {
    this.mutex = new Semaphore(1);
  }
  //enqueue can be process task, etc.
  private void enqueue(Object o) throws InterruptedException {
    mutex.acquire(); // critical section starts
    queue.add(o); //or custom operation
    mutex.release(); // critical section ends
  }

  //as pointed out in the comments this is more queue related
  private Object dequeue() throws InterruptedException {
    mutex.acquire(); // critical section starts
    Object o = queue.remove(0);//or custom operation
    mutex.release(); // critical section ends
    return o;
  }
}
Andrei T
  • 2,985
  • 3
  • 21
  • 28
  • There is a requirement for only one request at a time. How a queue will help with that? What purpose is there to have a queue with single element? Why not just an atomic ref then? – M. Prokhorov Apr 12 '18 at 09:51
  • The worse problem is that a queue delivers an item to a single consumer, but the requirement here is to deliver the same item to all consumers. This matches a `Future`, not a `Queue`. – Marko Topolnik Apr 12 '18 at 09:53
  • he asked about a solution. I am giving the solution that I applied in my sdk. It could be that using atomic ref might work. If you have a better solution just post it. – Andrei T Apr 12 '18 at 09:54
  • I have to admit in my case only set requests were blocked and not all. so It was a bit different. – Andrei T Apr 12 '18 at 09:57