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.
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.
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.
The CompletableFuture, was introduced in Java 8, provides an easy way to write asynchronous, non-blocking and multi-threaded code.
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.
@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).
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 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.
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... ...
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.
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());
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With