Java Multithreading
Learn Java Multithreading step by step with clear examples and exercises.
Why This Matters
Multithreading in Java is a powerful tool that enables you to execute multiple tasks concurrently within a single program. By leveraging multithreading, you can improve the performance of applications requiring complex computations or handling I/O operations such as web servers and games. This technique makes your programs more responsive, efficient, and capable of handling multiple requests simultaneously.
Prerequisites
To fully understand the concepts covered in this tutorial, you should have a good grasp of:
- Java basics, including variables, data types, loops, and control structures
- Object-oriented programming principles
- Exception handling in Java
- Understanding of synchronization and concurrent programming concepts (optional but recommended)
Core Concept
Understanding Threads
A thread is a separate path of execution within a program. In Java, every thread runs in its own independent context, known as a JVM stack. The main thread, or the thread that starts the application, is called the main thread.
public class MyThread extends Thread {
public void run() {
// Code to be executed by the thread goes here
}
}
In this example, we create a custom thread named MyThread. The run() method contains the code that will be executed when the thread starts.
Creating and Starting Threads
To create and start a new thread, you can either extend the Thread class or implement the Runnable interface. In this tutorial, we'll focus on extending the Thread class.
public class MyThread extends Thread {
public void run() {
// Code to be executed by the thread goes here
}
}
// Create a new instance of MyThread and start it
MyThread myThread = new MyThread();
myThread.start();
Managing Thread Lifecycle
The lifecycle of a Java thread consists of several states: New, Runnable, Blocked, Terminated, and Timed Waiting. Understanding these states can help you manage your threads effectively.
New
A new thread is an instance of the Thread class that has been created but not yet started.
MyThread myThread = new MyThread();
Runnable
When the start() method is called on a thread, it becomes Runnable. The JVM adds it to the pool of threads that are waiting to be executed.
myThread.start();
Blocked
A thread can become blocked due to various reasons such as waiting for I/O operations or synchronization locks. A blocked thread is not considered Runnable until it becomes unblocked.
Terminated
Once a thread finishes executing its run() method, it enters the Terminated state. At this point, the thread object can be garbage collected if no other part of your program references it.
Timed Waiting
A thread in the Timed Waiting state is waiting for a specific condition to occur within a specified time frame. Once the condition is met or the time elapses, the thread transitions back to the Runnable state.
Communicating Between Threads
Threads can communicate with each other using shared variables, synchronized blocks, or the wait(), notify(), and notifyAll() methods. These mechanisms help manage concurrent access to resources and ensure that threads execute in a coordinated manner.
Worked Example
Let's create an example where we print numbers from 1 to 100 using ten separate threads, each responsible for printing a range of 10 numbers.
public class RangeThread extends Thread {
private int start;
private int end;
public RangeThread(int start, int end) {
this.start = start;
this.end = end;
}
public void run() {
for (int i = start; i <= end; i++) {
System.out.println("Thread: " + Thread.currentThread().getName() + ", Number: " + i);
}
}
}
public class Main {
public static void main(String[] args) {
int numOfRanges = 10;
int rangeSize = 10;
RangeThread[] threads = new RangeThread[numOfRanges];
for (int i = 0; i < numOfRanges; i++) {
int start = (i * rangeSize) + 1;
int end = ((i + 1) * rangeSize > 100) ? 100 : (i + 1) * rangeSize;
threads[i] = new RangeThread(start, end);
}
for (RangeThread thread : threads) {
thread.start();
}
// Wait for all threads to finish before exiting the program
for (RangeThread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
In this example, we create an array of RangeThread objects, each responsible for printing a specific range of numbers. The main method creates the threads, starts them, and waits for them to finish before exiting the program using the join() method.
Common Mistakes
Not Synchronizing Access to Shared Resources
When multiple threads access a shared resource, it's essential to use synchronization to prevent race conditions and ensure proper execution order.
public class Counter {
private int count;
public synchronized void increment() {
count++;
}
}
In this example, we create a Counter class with a shared resource (the count variable). To ensure that the increments are performed in a thread-safe manner, we use synchronization on the increment() method.
Forgetting to Start the Thread
Starting the thread is necessary for it to begin executing its run() method. If you forget this step, your custom thread will not execute as intended.
MyThread myThread = new MyThread(); // Create a new instance of MyThread
// Forgetting to start the thread here: myThread.start();
Not Handling InterruptedException
When you call interrupt() on a thread, it sets an interrupted flag that can be checked within the thread's code. If you don't handle this exception, your program may not behave as expected when the thread is interrupted.
Practice Questions
- Write a Java program that prints the numbers from 1 to 100 using ten separate threads, each responsible for printing a range of 10 numbers.
- Solution: Follow the example provided in the worked example section.
- Implement a producer-consumer problem using two threads in Java, where one thread generates random integers and the other thread processes them.
- Solution: Create a
Bufferclass to store the produced integers, and use two threads—one for producing and another for consuming. Synchronize access to the buffer to prevent race conditions.
- Write a Java program that demonstrates the use of
wait(),notify(), andnotifyAll()methods to synchronize access to a shared resource between two threads.
- Solution: Create a
Resourceclass with a shared resource (e.g., an array). Implementwait(),notify(), andnotifyAll()methods in theResourceclass to manage access by multiple threads.
FAQ
Q: Can I run multiple main methods in a single Java application?
A: No, a Java program can only have one main method. However, you can create and start multiple threads within the main method.
Q: How do I join threads in Java?
A: The join() method allows you to wait for another thread to finish its execution before continuing with your code. You can call this method on a Thread object to make your current thread wait until the specified thread finishes.
Thread myThread = new MyThread();
myThread.start();
try {
myThread.join(); // Wait for myThread to finish before continuing
} catch (InterruptedException e) {
e.printStackTrace();
}
Q: What is the difference between sleep() and wait() in Java?
A: Both methods cause the current thread to pause its execution, but they serve different purposes. The sleep() method is a static method of the Thread class that causes the calling thread to sleep for a specified time (in milliseconds). The wait() method is an object-level method that makes the calling thread wait until it's notified or a specific condition occurs.
Q: How do I prioritize threads in Java?
A: In Java, you cannot directly set the priority of threads. However, you can use the setPriority() method to set the priority of a thread within a range from 1 (lowest) to 10 (highest). Keep in mind that higher-priority threads may not always execute before lower-priority ones due to complex scheduling algorithms used by the JVM.
Q: How do I create daemon threads in Java?
A: A daemon thread is a background thread that runs without preventing the JVM from exiting when all non-daemon threads have finished execution. You can create a daemon thread by calling the setDaemon() method on the Thread object before starting it.
MyThread myThread = new MyThread();
myThread.setDaemon(true); // Set the thread as a daemon thread
myThread.start();