Java Internals — Threads
We need threads in software applications for a variety of reasons, including:
- Responsiveness: Threads can allow an application to remain responsive to user input, even when performing time-consuming tasks such as loading data or processing complex algorithms. By running such tasks in a separate thread, the user interface can remain active and responsive, which improves the user experience.
- Concurrency: Threads allow multiple parts of a program to execute concurrently. For example, a web server can use threads to handle multiple requests at the same time, without blocking other requests.
- Performance: In some cases, using threads can improve performance by leveraging multiple processors or cores. For example, a program that performs heavy calculations can divide the work among multiple threads to take advantage of available resources.
- Modularity: Threads can make it easier to write modular code by separating different parts of the program into different threads. This can make the code easier to understand and maintain, and can also make it easier to debug.
Overall, threads are a powerful tool for building efficient, responsive, and scalable software applications. However, they also require careful design and management to avoid common issues such as race conditions, deadlocks, and resource contention.
A race condition is a common problem that can occur in multi-threaded programs when two or more threads access a shared resource in an uncoordinated way, leading to unpredictable and often incorrect behavior. Race conditions can occur in many different scenarios, such as when multiple threads are writing to the same file, accessing a shared data structure, or updating a shared counter. To avoid race conditions, programmers must use synchronization mechanisms such as locks, semaphores, or monitors to ensure that only one thread at a time can access the shared resource.
A deadlock is a situation in multi-threaded programming where two or more threads are blocked, waiting for each other to release a shared resource, and neither thread can proceed. In other words, each thread is stuck in a state of waiting for a resource that is held by another thread.
Resource contention is a situation that can occur in multi-threaded programming when multiple threads compete for access to a shared resource, such as a file, a network connection, or a critical section of code. Resource contention can lead to decreased performance, increased response time, and other issues.
In Java, threads can be created and managed using the built-in Thread class and related classes in the java.util.concurrent package. Here’s a simple example of creating and running a thread in Java:
public class MyThread extends Thread {
public void run() {
// Thread code goes here
System.out.println("Hello from MyThread!");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // Start the thread
System.out.println("Hello from Main!");
}
}
In addition to extending the Thread
class, there are two other ways to create a thread in Java:
- Implementing the
Runnable
interface: Instead of extending theThread
class, you can create a class that implements theRunnable
interface, which has a single method calledrun()
. Here's an example:
public class MyRunnable implements Runnable {
public void run() {
// Thread code goes here
System.out.println("Hello from MyRunnable!");
}
}
2. Using a lambda expression: Starting with Java 8, you can use a lambda expression to create and start a new thread. Here’s an example:
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
// Thread code goes here
System.out.println("Hello from lambda thread!");
});
thread.start(); // Start the thread
System.out.println("Hello from Main!");
}
}
If you are Spring Boot developer, actually you have used threads in your application several times. Suppose you have technical task that create service for sending email to user. In real world example there would be thousands of users which try to send emails simultaneously. What should we do in this case?
Email sending service in Spring Boot
We can separate each task for multiple threads and we can use the service without any interruption. In Spring Boot applications it is easy to separate tasks to multiple threads just using@Async
annotation.
@Async
is an annotation in Spring Framework that is used to indicate that a method should be executed asynchronously in a separate thread. When a method is annotated with @Async
, Spring Framework creates a new thread to execute the method, allowing it to run independently of the calling thread. To use the @Async
annotation, we need to enable asynchronous processing in the application by adding the @EnableAsync
annotation to the main Spring Boot application class. Once enabled, we can annotate any method with @Async
to indicate that it should run in a separate thread.
Create new Spring boot application from https://start.spring.io with dependencies:
Open project in your favorite IDE and annotate the main class of your application with @EnableAsync
annotation.
Next, we can create a service class that sends an email. We can annotate the sendEmail()
method with the @Async
annotation to indicate that it should run in a separate thread:
@Service
public class EmailService {
@Autowired
private JavaMailSender javaMailSender;
@Async
public void sendEmail(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
javaMailSender.send(message);
}
}
Finally, we can call the sendEmail()
method from another class or method in our application, and it will run asynchronously in a separate thread:
@RestController
public class MyController {
@Autowired
private EmailService emailService;
@GetMapping("/send-email")
public String sendEmail() {
emailService.sendEmail("recipient@example.com", "Test Email", "This is a test email.");
return "Email sent.";
}
}
But you need to add your email configuration properties before testing the endpoint. To add email properties to the configuration of a Spring Boot application, we can use the application.properties
file to set the properties for the email service:
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=your-email@gmail.com
spring.mail.password=your-email-password
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
In this example, we define an EmailService
class with a sendEmail()
method that sends an email using the JavaMailSender
interface from the Spring Framework. We annotate this method with the @Async
annotation to indicate that it should run in a separate thread. We then create a controller class MyController
with a sendEmail()
method that calls the sendEmail()
method of the EmailService
class. When we call the sendEmail()
method from a web browser, the email will be sent asynchronously in a separate thread.
By using the @Async
annotation, we can easily send emails asynchronously in a Spring Boot application, allowing us to improve the performance of the application and prevent long-running email sends from blocking the main thread.
That’s it for today. I hope you gained new skills for how to used thread in applications efficiently. Follow me to get more about java and spring boot.