Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is CompletableFuture guaranteed to run un a new thread?

Given some arbitrary context (e.g. Junit unit test, not specifically just not necessarily the "main" thread).

Would code like this have to introduce at least 2 threads ?

public static void main (String[] args)
{
    CompletableFuture<Void> s = new CompletableFuture<>();
    CompletableFuture<Void> f = new CompletableFuture<>();
    
    CompletableFuture<Void> someContext =  CompletableFuture.supplyAsync(() ->
    {
        try{    
          System.out.println(Thread.currentThread().getId());
          CompletableFuture<String> update =
          CompletableFuture.supplyAsync(
            () -> {
              String ans = null;
              try {
                System.out.println(Thread.currentThread().getId());
                ans = "Hello";
              } catch (Exception e) {
                ans = e.toString();
              } finally {
                s.complete(null);
                return ans;
              }
            });
          s.get();
          System.out.println(s.isDone());
        } catch (Exception e) {
            System.out.println("Some error");
            return null;
        }
        return null;
    });
    
    System.out.println(f.isDone());
}

When we get to s.get() in someContext can the JVM detect that it's waiting -> context switch to update complete it, and switch back to someContext?

When running it in ideone it consistently runs them in two diffrent threads but that's just single observation.

I would like to understand what guarantees the language/runtime provide.

like image 316
Darius Avatar asked Jun 24 '21 21:06

Darius


People also ask

Does CompletableFuture create new thread?

CompletableFuture executes these tasks in a thread obtained from the global ForkJoinPool. commonPool(). But we can also create a Thread Pool and pass it to runAsync() and supplyAsync() methods to let them execute their tasks in a thread obtained from our thread pool.

Are CompletableFuture thread safe?

CompletableFuture is inherently thread-safe The results of a write by one thread are guaranteed to be visible to a read by another thread only if the write operation happens-before the read operation.

Is Completable Future multithreaded?

The CompletableFuture, was introduced in Java 8, provides an easy way to write asynchronous, non-blocking and multi-threaded code.

Is CompletableFuture asynchronous?

CompletableFuture is used for asynchronous programming in Java. Asynchronous programming is a means of writing non-blocking code by running a task on a separate thread than the main application thread and notifying the main thread about its progress, completion or failure.

What is the difference between completablefuture () and new thread ()?

@Gerald Mücke already mentioned the important difference: CompletableFuture.runAsync (...) runs the Runnable in the forkJoin-Pool which is managed, while new Thread () creates a new thread which you have to manage. CompletableFuture will use threads managed by a ThreadPool (default or customized).

What is completablefuture in Java?

CompletableFuture is a feature for asynchronous programming using Java. Unlike procedural programming, asynchronous programming is about writing a non-blocking code by running all the tasks on separate threads instead of the main application thread and keep notifying the main thread about the progress, completion status, or if the task fails.

When a runnable is completed what happens to the thread?

When the runnable is completed, the thread can be reused for other runnables. This makes better usage of resources, especially as thread instantiation is an expensive operation - not only the object, but also some extra non-heap memory - the thread stack - has to be allocated. Show activity on this post.

How to use multi-threading in Spring Boot using completablefuture?

Multi-Threading in Spring Boot Using CompletableFuture Testing the Application. Run the Spring Boot Application. Once the server is started, test the POST endpoint. The sample... Conclusion. In this article, we've seen some typical use cases of the CompletableFuture. Let me know if you have any... ...


Video Answer


2 Answers

No, this guarantee does not exist.

What you are doing is unsafe.

If you look of the docs of CompletableFuture.supplyAsync(Supplier):

Returns a new CompletableFuture that is asynchronously completed by a task running in the ForkJoinPool.commonPool() with the value obtained by calling the given Supplier.

you can see that it uses a ForkJoinPool obtained from ForkJoinPool.commonPool().

From the docs of ForkJoinPool:

all threads in the pool attempt to find and execute tasks submitted to the pool and/or created by other active tasks (eventually blocking waiting for work if none exist). This enables efficient processing when most tasks spawn other subtasks (as do most ForkJoinTasks), as well as when many small tasks are submitted to the pool from external clients. Especially when setting asyncMode to true in constructors, ForkJoinPools may also be appropriate for use with event-style tasks that are never joined. All worker threads are initialized with Thread.isDaemon() set true. A static commonPool() is available and appropriate for most applications. The common pool is used by any ForkJoinTask that is not explicitly submitted to a specified pool. Using the common pool normally reduces resource usage (its threads are slowly reclaimed during periods of non-use, and reinstated upon subsequent use). For applications that require separate or custom pools, a ForkJoinPool may be constructed with a given target parallelism level; by default, equal to the number of available processors

This means that tasks submitted may be executed in an arbituary amount of threads (number of processors by default) and those threads are re-used. If all those threads are busy, the execution may wait for previous executions to finish.

As the common pool may also be used by other parts of the application, submitted tasks should run shortly and should not block so that other tasks can be executed quickly.

While OpenJDK has a special handling for ForkJoinPool in CompletableFuture#get that makes sure that other tasks can be executed during that time, other JDKs might not provide this.

Alternative: Asynchronous handling

Instead of blocking using .get(), you may want to use methods like CompletableFuture#thenAcceptAsync(Consumer). This runs the Consumer after the future finishes.

Also, you can use CompletionStage#exceptionally to handle exceptions in an asynchronous manner.

public static void main (String[] args) throws java.lang.Exception
{
    CompletableFuture<Void> s = new CompletableFuture();
    CompletableFuture<Void> f = new CompletableFuture();
    
    CompletableFuture<Void> someContext =  CompletableFuture.supplyAsync(() ->
    {
        
          System.out.println(Thread.currentThread().getId());
          CompletableFuture<String> update =
          CompletableFuture.supplyAsync(
            () -> {
              String ans = null;
              try {
                System.out.println(Thread.currentThread().getId());
                ans = "Hello";
              } catch (Exception e) {
                ans = e.toString();
              } finally {
                s.complete(null);
                return ans;
              }
            });
          s thenSupplyAsync(result->{
              System.out.println(s.isDone());
          }).exceptionally(e->{
              System.out.println("Some error");
              return null;
          });
        
        return null;
    });
    
    System.out.println(f.isDone());
}
like image 173
dan1st Avatar answered Oct 09 '22 12:10

dan1st


The answer is a little involved, as the correct one is - "it depends". In the configuration that you currently have, you will always get two threads; but that is an artifact of ForkJoinPool.

When a certain thread from ForkJoinPool blocks, internally a new one will be created, so that parallelism stays correct.

That is provable if we change your example to:

public static void main (String[] args) {

    ForkJoinPool pool = new ForkJoinPool(1);

    CompletableFuture<Void> s = new CompletableFuture<>();
    CompletableFuture<Void> f = new CompletableFuture<>();

    CompletableFuture<Void> someContext =  CompletableFuture.supplyAsync(() ->
    {
        try{
            System.out.println("someContextName : " + Thread.currentThread().getName());
            CompletableFuture<String> update =
                    CompletableFuture.supplyAsync(
                            () -> {
                                String ans = null;
                                try {
                                    System.out.println("update name : " + Thread.currentThread().getName());
                                    ans = "Hello";
                                } catch (Exception e) {
                                    ans = e.toString();
                                } finally {
                                    s.complete(null);
                                    return ans;
                                }
                            }, pool);
            s.get();
            System.out.println(s.isDone());
        } catch (Exception e) {
            System.out.println("Some error");
            return null;
        }
        return null;
    }, pool);

    System.out.println(f.isDone());

    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
}

Notice that even if ForkJoinPool is created with a single thread, this will still finish, as a new thread will be created.

On the other hand, if you change :

ExecutorService pool = Executors.newFixedThreadPool(1);

your code will block.

like image 2
Eugene Avatar answered Oct 09 '22 12:10

Eugene