Threads in Java
Introduction
In modern applications, responsiveness and efficiency are essential. Whether you’re building a desktop app, a game, or a backend server, you often need to perform multiple tasks simultaneously. This is where Threads in Java come into play. Threads allow you to run multiple tasks concurrently, making programs faster and more efficient.
In this blog, we’ll explore:
- What threads are and why they are important
- How to create and manage threads in Java
- The thread lifecycle
- Common problems like race conditions
- How to ensure thread safety
What is a Thread?
A thread is the smallest unit of execution in a program. While a process represents an entire running application, threads are lightweight sub-tasks inside that process.
👉 Example: When you use WhatsApp, one thread handles message sending, another handles receiving, while another manages notifications — all at the same time.
This is possible because of multithreading, which enables multiple threads to run concurrently in a single program.
Creating Threads in Java
Java provides two main ways to create threads:
1. Extending the Thread Class
public void run() {
System.out.println("Thread is running...");
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // starts the thread
}
}
2. Implementing the Runnable Interface
public void run() {
System.out.println("Thread is running...");
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start();
}
}
✅ In both cases, calling .start() will invoke the run() method in a separate thread.
Managing Thread Execution
Sometimes, you want to control how threads run. Java provides useful methods for this:
sleep(ms)– pauses the thread for a given time.join()– makes one thread wait until another finishes.
public void run() {
for(int i=1; i<=5; i++) {
try { Thread.sleep(500); } catch(Exception e) {}
System.out.println(i);
}
}
public static void main(String[] args) {
Demo t1 = new Demo();
Demo t2 = new Demo();
t1.start();
try { t1.join(); } catch(Exception e) {}
t2.start();
}
}
Here, t2 will start only after t1 finishes.
Thread Lifecycle
A thread doesn’t always run continuously. It passes through different states:
- New → Created but not started.
- Runnable → Ready to run but waiting for CPU.
- Running → Actively executing.
- Waiting/Timed Waiting → Paused temporarily (
sleep()/wait()). - Terminated → Finished execution.
Thread Safety and Synchronization
When multiple threads access a shared resource, problems can occur.
Example of Race Condition:
int count = 0;
public void increment() {
count++;
}
}
public class RaceConditionDemo {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for(int i=0; i<1000; i++) counter.increment();
});
Thread t2 = new Thread(() -> {
for(int i=0; i<1000; i++) counter.increment();
});
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("Final count: " + counter.count);
}
}
👉 Expected result is 2000, but due to race conditions, you may get less.
Fix with Synchronization
int count = 0;
public synchronized void increment() {
count++;
}
}
The synchronized keyword ensures only one thread modifies count at a time.
Key Takeaways
- Threads allow concurrent execution, improving efficiency.
- You can create threads using
ThreadorRunnable. - Methods like
sleep()andjoin()help manage thread execution. - Threads pass through different lifecycle states.
- Synchronization is crucial to avoid race conditions and ensure thread safety.
Conclusion
Multithreading is a powerful feature in Java that makes applications more responsive and efficient. However, working with threads requires careful management to avoid race conditions and deadlocks. As you gain experience, you’ll learn how to balance performance with safety when writing multithreaded programs.
Comments
Post a Comment