springspring-bootasynchronousspring-annotations

Spring Boot @Async use With @PostConstruct


Edit for Solution

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();
    }
}

Original Question

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!


Solution

  • 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