1. Java CompletableFuture detailed usage tutorial
Java 8 introduces a powerful asynchronous programming tool: CompletableFuture
. It provides a way to handle asynchronous computations, allowing you to retrieve results as they complete, or to combine the CompletableFuture
results of one or more. This section will analyze CompletableFuture
various aspects in detail, including creation, composition, exception handling, etc., and demonstrate its use through examples.
1.1 Create CompletableFuture
The easiest way to create CompletableFuture
is to use the no-argument constructor:
CompletableFuture<Void> future = new CompletableFuture<>();
This will create an unfinished one CompletableFuture
. You can complete
do it via method:
future.complete(null);
If you want to create an already completed one CompletableFuture
, you can use completedFuture
the method:
CompletableFuture<String> future = CompletableFuture.completedFuture("Hello, world!");
Additionally, you can use supplyAsync
methods to create an asynchronous computation CompletableFuture
:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 这里是一些长时间运行的计算
return "Hello, world!";
});
1.2 Processing the results of CompletableFuture
CompletableFuture
Provides a series of methods to process the results of asynchronous calculations. These methods each return a new one CompletableFuture
so you can chain them together to form a processing pipeline.
For example, you can use thenApply
methods to transform the result:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + ", world!");
You can also use thenAccept
methods to consume the results:
CompletableFuture.supplyAsync(() -> "Hello")
.thenAccept(s -> System.out.println(s + ", world!"));
If you don't care about the result and just want to perform some operations after the calculation is completed, you can use thenRun
the method:
CompletableFuture.supplyAsync(() -> "Hello")
.thenRun(() -> System.out.println("Computation finished."));
1.3 Combining CompletableFuture
CompletableFuture
A series of methods are provided to combine multiple asynchronous calculations.
For example, you can use thenCompose
methods to concatenate two asynchronous calculations:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + ", world!"));
You can also use thenCombine
methods to concatenate two asynchronous calculations:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> ", world!");
CompletableFuture<String> future = future1.thenCombine(future2, String::concat);
If you have more than one CompletableFuture
, you can use allOf
a method to wait for them all to complete:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> ", world!");
CompletableFuture<Void> future = CompletableFuture.allOf(future1, future2);
1.4 Handling CompletableFuture exceptions
CompletableFuture
Two methods are provided to handle exceptions: exceptionally
and handle
.
exceptionally
The method accepts a function that will be called when the calculation throws an exception, and its return value will be used as the new result:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("Exception!");
return "Hello, world!";
}).exceptionally(ex -> "Sorry, we have an error!");
handle
method is exceptionally
similar, but it handles both normal results and exceptions:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("Exception!");
return "Hello, world!";
}).handle((res, ex) -> {
if (ex != null) {
return "Sorry, we have an error!";
} else {
return res;
}
});
The above is CompletableFuture
the main usage. By combining these methods, you can create complex asynchronous processing pipelines, greatly improving the performance and responsiveness of your program.
2. Use CompletableFuture to combine MyBatis and thread pool to insert data in batches
When dealing with big data, we often need to insert a large amount of data in the database. If we use the traditional synchronous method, it may take a long time. In this part, I will show how to use Java for CompletableFuture
asynchronous processing, combined with MyBatis and thread pool to insert 300,000 pieces of data in batches, and improve the efficiency of data processing.
2.1 Create a thread pool
First, we need to create a thread pool to execute multiple insert tasks concurrently. We can use Java Executors
classes to create thread pools:
ExecutorService executor = Executors.newFixedThreadPool(10);
This will create a thread pool with 10 threads.
2.2 Create data
Then we need to create the data to be inserted. Let's say we want to insert some user data, each user has a name and an age:
class User {
private String name;
private int age;
// getters and setters...
}
We can create a method to generate user data:
List<User> generateUsers(int count) {
List<User> users = new ArrayList<>();
for (int i = 0; i < count; i++) {
User user = new User();
user.setName("User" + i);
user.setAge(i % 100);
users.add(user);
}
return users;
}
2.3 Insert data
Next, we need to create a method to insert data. We will use MyBatis SqlSession
to perform the insert operation:
void insertUsers(SqlSession sqlSession, List<User> users) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
for (User user : users) {
userMapper.insert(user);
}
sqlSession.commit();
}
Note that we committed the transaction after inserting all users. This is because transaction commit is an expensive operation in most databases and we should minimize the number of transaction commits.
2.4 Insert data using CompletableFuture
Now, we can CompletableFuture
insert data concurrently using We split the data into batches and create one CompletableFuture
for each batch to insert:
List<User> users = generateUsers(300000);
int batchSize = 1000;
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (int i = 0; i < users.size(); i += batchSize) {
List<User> batch = users.subList(i, Math.min(users.size(), i + batchSize));
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
insertUsers(sqlSession, batch);
}
}, executor);
futures.add(future);
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
In the above code, we first generated 300000 user data and then divided the data into batches of size 1000. For each batch, we create one CompletableFuture
to insert the data into and then CompletableFuture
add this to a list. Finally, we use CompletableFuture.allOf
to wait for everything CompletableFuture
to complete.
2.5 Close the thread pool
Finally, we need to shut down the thread pool. We can use ExecutorService.shutdown
the method to shut down the thread pool:
executor.shutdown();
This will wait for all submitted tasks to complete and then close the thread pool.
The above is CompletableFuture
the method of batch inserting data using MyBatis and thread pool. In this way, we can greatly improve the efficiency of inserting data. I hope this article can help everyone understand and use it better CompletableFuture
.