8

Context: I've read this SO thread discussing the differences between CompletableFuture and Thread.

But I'm trying to understand when should I use new Thread() instead of runAsync().

I understand that runAsyn() is more efficient for short/one-time parallel task because my program might avoid the overhead of creating a brand new thread, but should I also consider it for long running operations?

What are the factors that I should be aware before considering to use one over the other?

Thanks everyone.

Some_user_qwerty
  • 341
  • 1
  • 3
  • 10

2 Answers2

9

The difference between using the low-level concurrency APIs (such as Thread) and others is not just about the kind of work that you want to get done, it's also about the programming model and also how easy they make it to configure the environment in which the task runs.

In general, it is advisable to use higher-level APIs, such as CompletableFuture instead of directly using Threads.

I understand that runAsyn() is more efficient for short/one-time parallel task

Well, maybe, assuming you call runAsync, counting on it to use the fork-join pool. The runAsync method is overloaded with a method that allows one to specify the java.util.concurrent.Executor with which the asynchronous task is executed.

Using an ExecutorService for more control over the thread pool and using CompletableFuture.runAsync or CompletableFuture.supplyAsync with a specified executor service (or not) are both generally preferred to creating and running a Thread object directly.

There's nothing particularly for or against using the CompletableFuture API for long-running tasks. But the choice one makes to use Threads has other implications as well, among which:

  • The CompletableFuture gives a better API for programming reactively, without forcing us to write explicit synchronization code. (we don't get this when using threads directly)
  • The Future interface (which is implemented by CompletableFuture) gives other additional, obvious advantages.

So, in short: you can (and probably should, if the alternative being considered is the Thread API) use the CompletableFuture API for your long-running tasks. To better control thread pools, you can combine it with executor services.

ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • Thanks for the answer. So, is there any good reason to explicitly create a thread? From what you said it seams it's always advisable to use a thread pool. – Some_user_qwerty Jul 03 '18 at 13:54
  • 1
    @TheApprentice Of course there are good reasons, but the idea I am trying to convey is that whenever possible, one should prefer higher-level APIs. There are cases where one needs to do magic in thread classes, such as by extending the class... By all means, when it's necessary, it's appropriate. When it is not, other APIs are better suited. – ernest_k Jul 03 '18 at 14:03
  • But what exactly are those good reasons? Can you please provide an example? I'm trying to understand when it is appropriate to use an explicit thread instead of using high-level APIs. Thanks. – Some_user_qwerty Jul 03 '18 at 14:16
  • 1
    @TheApprentice That is exactly where the challenge is: it's the choice of the developer designing the code. There are no clear rules, but it can be simplified as "try the high level concurrency toolkit first, as a first and preferred choice. When they become insufficient, you'll know. You may need to customize low level info such as thread names, etc. In other words, the dev has the choice, but is also responsible for the trade-off" – ernest_k Jul 03 '18 at 14:34
  • I understand the problem then. Thanks for the help @ErnestKiwele ! – Some_user_qwerty Jul 03 '18 at 14:37
  • A little late to the party, but I would like to know if each runAsync() task is guaranteed to run by a dedicated thread. Or, can it be that multiple of these async future tasks are executed in the same thread? I am asking this, because I am retrieving the thread using Thread.CurrentThread() within the runAsync() body, giving it a custom name particularly for the async task, so I can distinguish the async tasks in logs. Thanks. – Kjeld May 27 '21 at 15:16
  • @Kjeld `CompletableFuture.runAsync(Runnable)` uses the fork-join pool. Depending on your system, a limited number of threads is available. So threads will be reused to execute your tasks when needed (which is very likely). If you really want to customize the creation of threads, then perhaps you should use the overload `CompletableFuture.runAsync(Runnable, ExecutorService)`, to which you can give a executor service with a thread factory that you use to customize the thread creation. – ernest_k May 28 '21 at 04:51
  • @ernest_k Thanks, but what I was asking is: does the default behaviour guarantee that one thread is used to do one sync task at a time, or can it be that the same thread is used to execute multiple tasks simultaneously? If this is unclear, or not guaranteed, then yes I could best specify my own Executor. – Kjeld May 28 '21 at 09:03
  • 1
    @Kjeld One thread will execute one task at a time. But it will be reused for other tasks when that first task is complete. You can test this by loading your executor in different patterns and printing thread names in your tasks. – ernest_k May 28 '21 at 09:11
6

The main difference is CompletableFuture run your task by default on the ForkJoinPool.commonPool. But if you create your own thread and start it will execute as a single thread, not on a Thread pool. Also if you want to execute some task in a sequence but asynchronously. Then you can do like below.

    CompletableFuture.runAsync(() -> {
        System.out.println("On first task");
        System.out.println("Thread : " + Thread.currentThread());
    }).thenRun(() -> {
        System.out.println("On second task");
    });

Output:

On first task
Thread : Thread[ForkJoinPool.commonPool-worker-1,5,main]
On second task

If you run the above code you can see that which pool CompletableFuture is using.

Note: Threads is Daemon in ForkJoinPool.commonPool.

Amit Bera
  • 7,075
  • 1
  • 19
  • 42
  • My question wasn't how to to use runAsync() or how it works since I already know that. I'm more concerned about design principles, when to use one or the other. I understand that one uses a thread pool and the other doesn't. But thanks for your time either way. – Some_user_qwerty Jul 03 '18 at 13:52