├── .gitignore
├── pom.xml
└── src
└── main
├── java
└── com
│ └── daluga
│ └── thread
│ ├── MultiThreadedExampleApplication.java
│ └── WorkerThread.java
└── resources
├── application.properties
└── logback.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse
2 | .classpath
3 | .project
4 | .settings/
5 |
6 | # Intellij
7 | .idea/
8 | *.iml
9 | *.iws
10 |
11 | # VSCode File
12 | .vscode/*
13 |
14 | # Mac
15 | .DS_Store
16 |
17 | # Maven
18 | log/
19 | target/
20 | .mvn
21 | mvnw*
22 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.daluga
7 | spring-boot-multithreaded-example
8 | 0.0.1-SNAPSHOT
9 | jar
10 |
11 | spring-boot-multithreaded-example
12 | Spring Boot Multithreaded Example
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-parent
17 | 2.7.2
18 |
19 |
20 |
21 |
22 | UTF-8
23 | 17
24 |
25 |
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter
30 |
31 |
32 |
33 | org.springframework.boot
34 | spring-boot-starter-test
35 | test
36 |
37 |
38 |
39 |
40 |
41 |
42 | org.springframework.boot
43 | spring-boot-maven-plugin
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/main/java/com/daluga/thread/MultiThreadedExampleApplication.java:
--------------------------------------------------------------------------------
1 | package com.daluga.thread;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.beans.factory.annotation.Value;
7 | import org.springframework.boot.CommandLineRunner;
8 | import org.springframework.boot.SpringApplication;
9 | import org.springframework.boot.autoconfigure.SpringBootApplication;
10 | import org.springframework.context.ApplicationContext;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 | import java.util.concurrent.*;
15 |
16 | @SpringBootApplication
17 | public class MultiThreadedExampleApplication implements CommandLineRunner {
18 |
19 | private static final Logger LOGGER = LoggerFactory.getLogger(MultiThreadedExampleApplication.class);
20 |
21 | @Autowired
22 | private ApplicationContext context;
23 |
24 | @Value("${number.of.requests}")
25 | private int numberOfRequests;
26 |
27 | @Value("${number.of.threads}")
28 | private int numberOfThreads;
29 |
30 | public static void main(String[] args) {
31 | SpringApplication.run(MultiThreadedExampleApplication.class, args);
32 | }
33 |
34 | @Override
35 | public void run(String... strings) throws Exception {
36 | LOGGER.debug("Spring Boot multithreaded example has started....");
37 | LOGGER.debug("Number of requests: " + numberOfRequests);
38 | LOGGER.debug("Number of threads: " + numberOfThreads);
39 | LOGGER.debug("Number of processors: " + Runtime.getRuntime().availableProcessors());
40 |
41 | ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
42 |
43 | // TODO: This does not seem to work and need to research why.
44 | // A java program can't terminate or exit while a normal thread is executing. So, left over threads
45 | // waiting for a never-satisfied event can cause problems. However, if you have blocks that need
46 | // to be executed (like a finally block to clean up resources) then you should not set daemon threads
47 | // to true. It all depends on the context of your solution.
48 | // ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads, threadFactory -> {
49 | // Thread t = new Thread();
50 | // t.setDaemon(true);
51 | // return t;
52 | // });
53 |
54 | List tasks = new ArrayList<>(numberOfRequests);
55 |
56 | // --------------------------------------------------------------------------------------------------------------
57 | // Notes for multithreading separate worker tasks.
58 | // --------------------------------------------------------------------------------------------------------------
59 | // 1. You need to create the WorkerThread from the spring context so that bean will be properly injected with
60 | // it's dependencies. You cannot use this: WorkerThread wt = new WorkerThread();
61 | // 2. The WorkerThread class must implement the Callable interface so that it can be executed in a separate
62 | // thread.
63 | // 3. The WorkerThread class needs to have a scope of prototype so that it is not a singleton. The singleton
64 | // scope is the default scope for a Spring bean. As a result, this class must have this annotation:
65 | // @Scope("prototype")
66 | // --------------------------------------------------------------------------------------------------------------
67 |
68 | for (int i = 0; i < numberOfRequests; i++) {
69 | WorkerThread wt = context.getBean(WorkerThread.class, String.valueOf(i));
70 | tasks.add(wt);
71 | }
72 |
73 | // The problem with this approach is that this blocks until all threads have completed. As a result, you need
74 | // to wait before all threads have executed before you see any of the results in the get() method.
75 | List> futures = executorService.invokeAll(tasks);
76 |
77 | for (Future future : futures) {
78 | String result = future.get(10000, TimeUnit.MILLISECONDS);
79 | LOGGER.debug("Thread reply results [" + result + "]");
80 | }
81 |
82 | executorService.shutdown();
83 |
84 | LOGGER.debug("Spring Boot multithreaded example has ended....");
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/com/daluga/thread/WorkerThread.java:
--------------------------------------------------------------------------------
1 | package com.daluga.thread;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.context.annotation.Scope;
6 | import org.springframework.stereotype.Component;
7 |
8 | import java.util.UUID;
9 | import java.util.concurrent.Callable;
10 |
11 | // We need a scope of prototype so that a new instance is created each time. The default scope is singleton so you would always get the same instance
12 | // if we did not specify a scope of prototype.
13 |
14 | @Component
15 | @Scope("prototype")
16 | public class WorkerThread implements Callable {
17 |
18 | private static final Logger LOGGER = LoggerFactory.getLogger(WorkerThread.class);
19 |
20 | private String request;
21 |
22 | public WorkerThread(String request) {
23 | this.request = request;
24 | }
25 |
26 | @Override
27 | public String call() throws Exception {
28 | LOGGER.debug("Thread started [" + request + "]");
29 | return doWork();
30 | }
31 |
32 | private String doWork() {
33 | try {
34 | Thread.sleep(5000);
35 | } catch (InterruptedException e) {
36 | LOGGER.error("An unexpected interrupt exception occurred!", e);
37 | }
38 |
39 | return "Request [" + request + "] " + createUUID();
40 | }
41 |
42 | private String createUUID() {
43 | UUID id = UUID.randomUUID();
44 | return id.toString();
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | number.of.requests=100
2 | number.of.threads=5
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | System.out
5 |
6 | %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - [%thread] - %m%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------