Imagine you're downloading a file while listening to music on your computer. The download doesn't have to completely finish before the music can play. Both tasks can happen at the same time.
This is the basic idea behind multithreading.
Multithreading allows a Java program to perform multiple tasks concurrently by using multiple threads.
A thread is a small unit of execution inside a program.
You can think of a thread as a worker. If your program has one worker, it can work on one task at a time. If it has multiple workers, different tasks can make progress concurrently.
What Is a Thread?
When you run a Java program, Java creates a thread to execute your code. This is commonly called the main thread.
For example:
class Main {
public static void main(String[] args) {
System.out.println("Hello, John!");
}
}The main() method runs on the main thread.
You can think of it like:
Java Program
|
Main Thread
|
main()If we create additional threads, our program can have multiple execution paths.
Java Program
|
├── Main Thread
|
├── Thread 1
|
└── Thread 2Why Do We Need Multithreading?
Imagine a program that needs to perform three tasks:
Download a file
Process some data
Show a progress indicatorIf everything happens on a single thread, one task may have to wait for another.
With multiple threads, different tasks can make progress concurrently.
For example:
Thread 1 → Download file
Thread 2 → Process data
Thread 3 → Update progressThis can make applications more responsive and efficient, especially when tasks involve waiting for things such as network operations or file operations.
Creating a Thread
Java provides a Thread class that we can use to create threads.
For example:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running.");
}
}The run() method contains the code that the new thread will execute.
We can create an object:
MyThread thread = new MyThread();But simply creating the object does not start a new thread.
We need to call:
thread.start();For example:
class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
System.out.println("Main thread is running.");
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("New thread is running.");
}
}The output order can vary:
Main thread is running.
New thread is running.or:
New thread is running.
Main thread is running.This happens because both threads can execute independently, and Java doesn't guarantee which one will print first.
start() vs run()
This is an important point for beginners.
When we write:
thread.start();Java starts a new thread, which then executes the run() method.
But if we directly call:
thread.run();we are simply calling a normal method. It does not start a new thread.
So remember:
start()→ starts a new thread
run()→ contains the code executed by the thread
Creating a Thread Using Runnable
Another common way to create a thread is by implementing the Runnable interface.
For example:
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("Task is running.");
}
}Then:
class Main {
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread = new Thread(task);
thread.start();
}
}Output:
Task is running.This approach is often preferred because Java allows a class to extend only one class, while it can implement multiple interfaces.
Using a Lambda
Since Runnable is a functional interface, we can make the code even shorter using a lambda expression.
Thread thread = new Thread(() -> {
System.out.println("Task is running.");
});
thread.start();This is a very common way of creating a simple thread.
The lambda:
() -> {
System.out.println("Task is running.");
}provides the implementation of Runnable's run() method.
Running Multiple Threads
We can create multiple threads in the same program.
class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("Downloading file...");
});
Thread thread2 = new Thread(() -> {
System.out.println("Playing music...");
});
thread1.start();
thread2.start();
}
}Both threads can execute concurrently.
The output order isn't guaranteed. You might see:
Downloading file...
Playing music...or:
Playing music...
Downloading file...This is normal in multithreaded programs.
Thread Sleep
Sometimes we want a thread to pause for a certain amount of time.
We can use Thread.sleep().
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("Task finished.");
} catch (InterruptedException e) {
System.out.println("Thread was interrupted.");
}
});
thread.start();Here:
Thread.sleep(2000);pauses the thread for approximately 2 seconds.
The value is specified in milliseconds:
1000 milliseconds = 1 second
2000 milliseconds = 2 secondsGetting the Current Thread
We can use Thread.currentThread() to get the thread that is currently executing the code.
System.out.println(Thread.currentThread().getName());For the main() method, the output is commonly:
mainWe can also give a thread a name:
Thread thread = new Thread(() -> {
System.out.println("Task running");
});
thread.setName("DownloadThread");
thread.start();Inside the thread, we can print its name:
System.out.println(Thread.currentThread().getName());Output:
DownloadThreadThread names can be very helpful when debugging multithreaded applications.
A Real-World Example
Imagine you're building a music application.
When the user presses the play button, the application may need to:
Play music
Download album information
Update the user interfaceIf a long-running operation blocks the main thread, the application can become slow or unresponsive.
Using separate threads can allow different tasks to make progress concurrently.
This is especially important in applications that perform network requests, file operations, background calculations, or other tasks that might take some time.
Multithreading Does Not Always Mean "Exactly at the Same Time"
One small but important point: concurrency and parallelism are not exactly the same thing.
With concurrency, multiple tasks can make progress during overlapping periods.
With parallelism, multiple tasks are actually executing at the same time, typically on different CPU cores.
Java's threading system can support both, depending on the hardware and how the application is running.
For beginners, you can simply think of multithreading as allowing different tasks to run concurrently.
A Complete Example
Let's create two threads that perform different tasks:
class Main {
public static void main(String[] args) {
Thread downloadThread = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("Downloading: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("Download interrupted.");
}
}
});
Thread musicThread = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("Playing music: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("Music interrupted.");
}
}
});
downloadThread.start();
musicThread.start();
}
}The output may look something like:
Downloading: 1
Playing music: 1
Downloading: 2
Playing music: 2
Downloading: 3
Playing music: 3
...The exact order can vary because the two threads are executing independently.
The main idea is:
Multithreading allows a Java program to have multiple threads that can perform different tasks concurrently.
For now, the most important things to understand are Thread, Runnable, start(), run(), and the idea that multiple threads can execute independently. Later, when working with multithreading in more depth, you'll encounter topics such as synchronization, race conditions, thread pools, and ExecutorService.