A callback is a function that is passed as an argument to another function and is called when a certain event occurs. In the context of asynchronous programming, a callback is often used to handle the result of an asynchronous operation.
A Future
represents the result of an asynchronous computation. It provides methods to check if the computation is complete, wait for its completion, and retrieve the result.
CompletableFuture
is a more advanced and flexible alternative to Future
in Java. It allows you to chain multiple asynchronous operations together and handle exceptions more gracefully.
public class ThreadExample {
public static void main(String[] args) {
Thread asyncTask = new Thread(() -> {
try {
// Simulate a long - running task
Thread.sleep(2000);
System.out.println("Asynchronous task completed");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
asyncTask.start();
System.out.println("Main thread continues");
}
}
In this example, we create a new Thread
object and start it. The main thread continues its execution while the asynchronous task runs in the background.
import java.util.concurrent.*;
public class FutureExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
Thread.sleep(2000);
return "Task result";
});
try {
if (future.isDone()) {
System.out.println(future.get());
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executor.shutdown();
}
}
Here, we use an ExecutorService
to manage the execution of the asynchronous task. The submit
method returns a Future
object, which we can use to check the status of the task and retrieve the result.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "CompletableFuture result";
});
future.thenAccept(result -> System.out.println(result));
try {
CompletableFuture.allOf(future).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
In this example, we use CompletableFuture.supplyAsync
to start an asynchronous task. The thenAccept
method is used to define a callback that will be executed when the task is completed.
When using asynchronous programming, it is crucial to handle errors properly. With CompletableFuture
, you can use the exceptionally
method to handle exceptions.
import java.util.concurrent.CompletableFuture;
public class ErrorHandlingExample {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Simulated error");
});
future.exceptionally(ex -> {
System.out.println("Error occurred: " + ex.getMessage());
return "Default result";
}).thenAccept(result -> System.out.println(result));
}
}
You can chain multiple CompletableFuture
operations together using methods like thenApply
, thenCompose
, etc.
import java.util.concurrent.CompletableFuture;
public class ChainingExample {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Initial result")
.thenApply(result -> result + " - Processed")
.thenApply(finalResult -> finalResult + " - Final");
future.thenAccept(System.out::println);
}
}
Creating too many threads can lead to resource exhaustion. Use thread pools provided by ExecutorService
to manage the number of threads effectively.
CompletableFuture
provides a rich set of methods for chaining operations, handling exceptions, and combining results. It is suitable for complex asynchronous workflows.
Blocking operations in callbacks can defeat the purpose of asynchronous programming. Try to use non - blocking alternatives whenever possible.
Asynchronous programming in Java is a powerful technique that can significantly improve the performance and responsiveness of your applications. By understanding the fundamental concepts, usage methods, common practices, and best practices, you can effectively use asynchronous programming in your Java projects. Whether you are dealing with I/O - bound tasks or long - running computations, Java provides various tools such as threads, Future
, and CompletableFuture
to help you implement asynchronous operations.