Home > Software design >  Thread vs Runnable vs CompletableFuture in Java multi threading
Thread vs Runnable vs CompletableFuture in Java multi threading

Time:01-05

I am trying to implement multi threading in my Spring Boot app. I am just beginner on multi threading in Java and after making some search and reading articles on various pages, I need to be clarified about the following points. So;

1. As far as I see, I can use Thread, Runnable or CompletableFuture in order to implement multi threading in a Java app. CompletableFuture seems a newer and cleaner way, but Thread may have more advantages. So, should I stick to CompletableFuture or use all of them based on the scenario?

2. Basically I want to send 2 concurrent requests to the same service method by using CompletableFuture:

CompletableFuture<Integer> future1 = fetchAsync(1);
CompletableFuture<Integer> future2 = fetchAsync(2);

int result1 = future1.get();
int result2 = future2.get();

How can I send these request concurrently and then return result based on the following condition:

  • if the first result is not null, return result and stop process
  • if the first result is null, return the second result and stop process

How can I do this? Should I use CompletableFuture.anyOf() for that?

CodePudding user response:

So, should I stick to CompletableFuture or use all of them based on the scenario?

Well, they all have different purposes and you'll probably use them all either directly or indirectly:

  • Thread represents a thread and while it can be subclassed in most cases you shouldn't do so. Many frameworks maintain thread pools, i.e. they spin up several threads that then can take tasks from a task pool. This is done to reduce the overhead that thread creation brings as well as to reduce the amount of contention (many threads and few cpu cores mean a lot of context switches so you'd normally try to have fewer threads that just work on one task after another).
  • Runnable was one of the first interfaces to represent tasks that a thread can work on. Another is Callable which has 2 major differences to Runnable: 1) it can return a value while Runnable has void and 2) it can throw checked exceptions. Depending on your case you can use either but since you want to get a result, you'll more likely use Callable.
  • CompletableFuture and Future are basically a way for cross-thread communication, i.e. you can use those to check whether the task is done already (non-blocking) or to wait for completion (blocking).

So in many cases it's like this:

  • you submit a Runnable or Callable to some executor
  • the executor maintains a pool of Threads to execute the tasks you submitted
  • the executor returns a Future (one implementation being CompletableFuture) for you to check on the status and results of the task without having to synchronize yourself.

However, there may be other cases where you directly provide a Runnable to a Thread or even subclass Thread but nowadays those are far less common.

How can I do this? Should I use CompletableFuture.anyOf() for that?

CompletableFuture.anyOf() wouldn't work since you'd not be able to determine which of the 2 you'd pass in was successful first.

Since you're interested in result1 first (which btw can't be null if the type is int) you basically want to do the following:

Integer result1 = future1.get(); //block until result 1 is ready
if( result1 != null ) {
  return result1;
} else {
  return future2.get(); //result1 was null so wait for result2 and return it
}

You'd not want to call future2.get() right away since that would block until both are done but instead you're first interested in future1 only so if that produces a result you wouldn't have for future2 to ever finish.

Note that the code above doesn't handle exceptional completions and there's also probably a more elegant way of composing the futures like you want but I don't remember it atm (if I do I'll add it as an edit).

Another note: you could call future2.cancel() if result1 isn't null but I'd suggest you first check whether cancelling would even work (e.g. you'd have a hard time really cancelling a webservice request) and what the results of interrupting the service would be. If it's ok to just let it complete and ignore the result that's probably the easier way to go.

CodePudding user response:

So, should I stick to CompletableFuture or use all of them based on the scenario?

Use the one that is most appropriate to the scenario. Obviously, we can't be more specific unless you explain the scenario.

There are various factors to take into account. For example:

  • Thread Runnable doesn't have a natural way to wait for / return a result. (But it is not hard to implement.)
  • Repeatedly creating bare Thread objects is inefficient because thread creation is expensive. Thread pooling is better but you shouldn't implement a thread pool yourself.
  • Solutions that use an ExecutorService take care of thread pooling and allow you to use Callable and return a CompletableFuture. But for a once-off async computation this might be over-kill.

As you can see ... there is no single correct answer for all scenarios.


Should I use CompletableFuture.anyOf() for that?

No. The logic of your example requires that you must have the result for future1 to determine whether or not you need the result for future2. So the solution is something like this:

Integer i1 = future1.get();
if (i1 == null) {
    return future2.get();
} else {
    future2.cancel();
    return i1;
}

It will be more complicated if you need to deal with exceptions and/or timeouts. There is also the caveat that the second computation may ignore the interrupt and continue on regardless.

  • Related