This ended up being my @Service
class with multi-threading working. It populates each table in the DB at the same time. No longer needed the @Async
annotations or task executor with this solution.
@Service
public class AsyncDBLoad extends Thread {
final CovidDataServices service;
Thread.UncaughtExceptionHandler exceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread th, Throwable ex) {
System.out.println("Uncaught exception: " + ex.getMessage()
+ "\nIn Thread: " + th.getName());
}
};
public AsyncDBLoad(CovidDataServices service) {
this.service = service;
}
@PostConstruct
public void initializeAsyncDB() throws IOException, InterruptedException {
// Thread to populate state DB
Thread stateThread = new Thread() {
@Override
public void run() {
try {
service.populateDbWithStateData();
} catch (InterruptedException | IOException e) {
System.out.println("State thread error");
}
}
};
// Thread to populate country DB
Thread countryThread = new Thread() {
@Override
public void run() {
try {
service.populateDBWithCountryData();
} catch (InterruptedException | IOException e) {
System.out.println("Country thread error");
}
}
};
// set error handlers
stateThread.setUncaughtExceptionHandler(exceptionHandler);
countryThread.setUncaughtExceptionHandler(exceptionHandler);
// set names
stateThread.setName("State Thread");
countryThread.setName("Country Thread");
// start the threads
stateThread.start();
countryThread.start();
}
}
I am trying to get 2 @PostConstruct
methods in a @Service
to work with @Async
.
Specifically on startup I am populating DB tables and these are separate from each other and could be loaded concurrently.
I have tried to follow along here and got the first @PostConstruct
to run asynchronously but the second method still waits for the first one to be completed before starting.
This is currently what I have for code.
Application start:
@SpringBootApplication
@EnableAsync
public class Covid19Trackerv2Application {
public static void main(String[] args) {
SpringApplication.run(Covid19Trackerv2Application.class, args);
}
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("GithubLookup-");
executor.initialize();
return executor;
}
}
@Service
to call @Async
methods:
@Service
public class AsyncDBLoad {
final CovidDataServices service;
public AsyncDBLoad(CovidDataServices service) {
this.service = service;
}
@PostConstruct
public void initializeAsyncDB() throws IOException, InterruptedException {
service.populateDBWithCountryData();
}
@PostConstruct
public void initializeAsyncDB2() throws IOException, InterruptedException {
service.populateDbWithStateData();
}
}
Relevant parts of the CovidDataServices @Service
that holds the @Async
methods:
@Async
public void populateDbWithStateData() throws IOException, InterruptedException {
System.out.println("Start state DB population");
// part of the code that populates the DB and takes around 15 seconds if DB is empty
System.out.println("exit state DB population");
}
@Async
public void populateDBWithCountryData() throws IOException, InterruptedException {
System.out.println("Start country DB population");
// part of the code that populates the DB and takes around 15 seconds if DB is empty
System.out.println("exit country DB population");
}
Log output of startup:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.3.RELEASE)
2020-10-03 10:29:50.222 INFO 9636 --- [ main] c.c.Covid19Trackerv2Application : Starting Covid19Trackerv2Application on DESKTOP-AE0J8AT with PID 9636 (C:\Users\bhade\Documents\covid-19-trackerv2\build\classes\java\main started by bhade in C:\Users\bhade\Documents\covid-19-trackerv2)
2020-10-03 10:29:50.224 INFO 9636 --- [ main] c.c.Covid19Trackerv2Application : No active profile set, falling back to default profiles: default
2020-10-03 10:29:50.636 INFO 9636 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data MongoDB repositories in DEFAULT mode.
2020-10-03 10:29:50.677 INFO 9636 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 37ms. Found 2 MongoDB repository interfaces.
2020-10-03 10:29:51.352 INFO 9636 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-10-03 10:29:51.361 INFO 9636 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-10-03 10:29:51.361 INFO 9636 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.37]
2020-10-03 10:29:51.429 INFO 9636 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-10-03 10:29:51.429 INFO 9636 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1164 ms
2020-10-03 10:29:51.561 INFO 9636 --- [ main] org.mongodb.driver.cluster : Cluster created with settings {hosts=[localhost:27017], mode=SINGLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms'}
2020-10-03 10:29:51.609 INFO 9636 --- [localhost:27017] org.mongodb.driver.connection : Opened connection [connectionId{localValue:1, serverValue:78}] to localhost:27017
2020-10-03 10:29:51.614 INFO 9636 --- [localhost:27017] org.mongodb.driver.cluster : Monitor thread successfully connected to server with description ServerDescription{address=localhost:27017, type=STANDALONE, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=8, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=2658300}
2020-10-03 10:29:51.843 INFO 9636 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
2020-10-03 10:29:51.847 INFO 9636 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService
2020-10-03 10:29:51.847 INFO 9636 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'taskExecutor'
Start country DB population
2020-10-03 10:29:51.894 INFO 9636 --- [ scheduling-1] org.mongodb.driver.connection : Opened connection [connectionId{localValue:2, serverValue:79}] to localhost:27017
2020-10-03 10:29:52.086 INFO 9636 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-10-03 10:29:52.095 INFO 9636 --- [ main] c.c.Covid19Trackerv2Application : Started Covid19Trackerv2Application in 2.234 seconds (JVM running for 2.525)
exit country DB population
Start state DB population
exit state DB population
The log shows that the first @PostConstruct
gets called, but the application continues to load. The second @PostConstruct
does not get called until the first one is finished. I have also tried putting the method calls in the same @PostConstruct
like below which gave the same results.
@PostConstruct
public void initializeAsyncDB() throws IOException, InterruptedException {
service.populateDBWithCountryData();
service.populateDbWithStateData();
}
Is there a way to do this?
Any help is much appreciated!
How about starting both populate methods in separate Threads?
@PostConstruct
void xx() {
new Thread(service::a).start();
new Thread(service::b).start();
}
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@SpringBootApplication
public class PostConstructApp {
public static void main(String[] args) { SpringApplication.run(PostConstructApp.class, args); }
@Component
@RequiredArgsConstructor
class C {
final MyService service;
@PostConstruct
void xx() {
new Thread(service::a).start();
new Thread(service::b).start();
}
}
@Component
class MyService {
@SneakyThrows
void a() {
System.out.println("A");
Thread.sleep(10000);
System.out.println("DONE A");
}
@SneakyThrows
void b() {
System.out.println("B");
Thread.sleep(5000);
System.out.println("DONE B");
}
}
}
Logs: A and B are printed together and DONE B and DONE A after a whlie:
2020-10-03 14:07:13.640 INFO 15974 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.38]
2020-10-03 14:07:13.687 INFO 15974 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-10-03 14:07:13.687 INFO 15974 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 544 ms
A
B
2020-10-03 14:07:14.018 INFO 15974 --- [ main] s.psotcons.PostConstructApp : Started PostConstructApp in 1.046 seconds (JVM running for 1.341)
DONE B
DONE A