Introduction to Threads
By the Syncfusion DevTutorials team · Last updated
Learn processes vs threads, the thread lifecycle, creating threads with Thread and Runnable, and using start, run, sleep, join and priorities.
Think of a restaurant kitchen.
If one cook had to do everything, the meal would take a long time. First they would chop the vegetables, then boil the rice, then fry the fish, then make the sauce, one after another.
In a real kitchen, several cooks work at the same time. One chops, one stirs the rice, and one fries the fish. They share the same kitchen, the same fridge, and the same stove. Dinner is ready much faster.
A Java program can work in the same way. Each "cook" is called a thread. Several threads can run inside one program and share its memory.
In simple words, a thread is one path of work inside a program, and multithreading means running several of those paths at the same time.
What You Will Learn
In this lesson, you will learn:
- What processes and threads are, and how they are different.
- Why programs use multithreading.
- The states in a thread's life.
- How to create threads with the
Threadclass and theRunnableinterface. - The difference between
start()andrun(). - How
sleep()pauses a thread, and howjoin()waits for one to finish. - How thread priorities work.
Start With a Working Example
Read the code, then look at the output below it.
A second thread does work while main waits for it
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
int total = 0;
for (int i = 1; i <= 100; i++) {
total += i;
}
System.out.println("Worker: the sum of 1 to 100 is " + total);
});
System.out.println("Main: starting the worker");
worker.start();
worker.join();
System.out.println("Main: the worker has finished");
}
}
Output:
Main: starting the worker
Worker: the sum of 1 to 100 is 5050
Main: the worker has finished
main creates a second thread called worker and gives it a job as a lambda. start() begins the thread. join() makes main wait until the worker is done.
Because main waits, the lines always appear in this order.
Try This
Delete the line worker.join(); and run the program on your own computer a few times. Sometimes "the worker has finished" prints before the worker's own line. Without join, main does not wait.
The in-browser Java runtime in this course runs on a single thread. Thread code compiles and runs here, but the threads take turns instead of truly running at the same time. Every example in this lesson waits for its threads with join(), so it prints the same result in the browser and on a real computer.
What is a Process?
A process is a running program.
When you open a web browser, a music player, and a text editor, your computer runs three processes. Each process:
- has its own memory, which other processes cannot touch;
- has its own resources, such as open files;
- is managed by the operating system.
When you run a Java program, the operating system starts one process for the JVM.
What is a Thread?
A thread is a single path of work inside a process. It runs one line of code after another.
Every Java program starts with one thread, called the main thread. It is the thread that runs your main method.
A process can create more threads. All the threads in a process:
- share the same memory, so they can read and change the same objects;
- each have their own call stack, so each runs its own methods with its own local variables.
One Java process
┌───────────────────────────────────┐
│ Shared memory (heap): objects │
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ main │ │ thread │ │ thread │ │
│ │ thread │ │ 2 │ │ 3 │ │
│ │ (stack)│ │ (stack)│ │ (stack)│ │
│ └────────┘ └────────┘ └────────┘ │
└───────────────────────────────────┘
You can ask Java which thread is running a line of code with Thread.currentThread().getName().
Process vs Thread
| Process | Thread | |
|---|---|---|
| What it is | A running program | A path of work inside a process |
| Memory | Its own, separate from others | Shared with the other threads in its process |
| Created by | The operating system | The program, inside its process |
| Cost to create | Higher | Lower |
| Talking to others | Hard, needs special channels | Easy, through shared objects |
| If one crashes | Other processes keep running | It can affect the whole process |
| Example | Your browser | One browser tab loading a page |
Think of a process as a house. Threads are the people living in it. Each house has its own rooms and doors. The people in one house share the kitchen and the living room.
Why Multithreading Is Used
Multithreading means running several threads in one program at the same time.
To Stay Responsive
A music app plays a song on one thread while you search for the next song on another. Without threads, the music would stop every time you typed.
To Use Waiting Time
Programs often wait: for a file to load, a web page to answer, or a database to reply. While one thread waits, others can keep working.
To Use Every CPU Core
Modern computers have several cores. Each core can run one thread at a time. A program with one thread uses only one core. A program with several threads can use them all and finish big jobs faster.
To Keep Code Organised
Separate jobs, like "save a backup every minute" and "serve users", can each live in their own thread.
| Real application | What the threads do |
|---|---|
| Web server | One thread per user request, so many users are served at once |
| Video game | One thread draws the screen, another plays sound, another handles input |
| Download manager | Several threads download different parts of a file at once |
| Text editor | One thread for typing, one for spell-checking in the background |
Thread Lifecycle
A thread moves through several states from creation to finish. Java names them in the Thread.State enum.
| State | Meaning |
|---|---|
NEW | Created with new, but start() has not been called yet |
RUNNABLE | Started; running, or ready to run as soon as a core is free |
BLOCKED | Waiting to get a lock that another thread holds |
WAITING | Waiting with no time limit, for example inside join() |
TIMED_WAITING | Waiting for a set time, for example inside sleep(500) |
TERMINATED | Finished; its run method has ended |
start()
NEW ───────────► RUNNABLE ─────────────────► TERMINATED
▲ │ run() ends
│ │
lock is free / │ │ waits for a lock, sleep(), join()
time is up / │ ▼
other thread BLOCKED / WAITING / TIMED_WAITING
finished
A thread can move between RUNNABLE and the waiting states many times. But it starts NEW exactly once, and once it is TERMINATED, it can never run again.
You can read a thread's state with getState(). Read the code, then look at the output below it.
A thread starts NEW and ends TERMINATED
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> System.out.println(" ...the thread is doing its job"));
System.out.println("Before start: " + t.getState());
t.start();
t.join();
System.out.println("After join: " + t.getState());
System.out.println("Still alive? " + t.isAlive());
}
}
Output:
Before start: NEW
...the thread is doing its job
After join: TERMINATED
Still alive? false
The states in between change very quickly, so they are hard to catch in a print statement. NEW and TERMINATED are certain, because one is before start() and the other is after join().
isAlive() returns true while a thread has started but not yet finished.
Creating Threads
There are two common ways to give a thread its job.
Thread Class
Extend the Thread class and override its run() method. The run() method holds the thread's job. Read the code, then look at the output below it.
Create a thread by extending Thread
public class Main {
public static void main(String[] args) throws InterruptedException {
Downloader d = new Downloader("report.pdf");
d.start();
d.join();
System.out.println("main: download thread is done");
}
}
class Downloader extends Thread {
private final String file;
Downloader(String file) {
this.file = file;
}
@Override
public void run() {
for (int percent = 25; percent <= 100; percent += 25) {
System.out.println("Downloading " + file + ": " + percent + "%");
}
}
}
Output:
Downloading report.pdf: 25%
Downloading report.pdf: 50%
Downloading report.pdf: 75%
Downloading report.pdf: 100%
main: download thread is done
Downloader is a Thread, so you can call start() on it directly.
Runnable Interface
Implement the Runnable interface and its one method, run(). Then pass the Runnable object to a Thread.
Runnable is a functional interface, so you can also write it as a lambda. Read the code, then look at the output below it.
Create threads with Runnable, as a class and as a lambda
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Greeter("Asha"), "greeter-1");
Runnable task = () -> System.out.println("Hello from " + Thread.currentThread().getName());
Thread t2 = new Thread(task, "greeter-2");
t1.start();
t1.join();
t2.start();
t2.join();
System.out.println("Main thread is called: " + Thread.currentThread().getName());
}
}
class Greeter implements Runnable {
private final String name;
Greeter(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("Hello, " + name + "! I run on " + Thread.currentThread().getName());
}
}
Output:
Hello, Asha! I run on greeter-1
Hello from greeter-2
Main thread is called: main
The second argument to new Thread(...) gives the thread a name. That makes output and error messages much easier to read.
Thread Class vs Runnable Interface
Extend Thread | Implement Runnable | |
|---|---|---|
| How | class X extends Thread | class X implements Runnable, or a lambda |
| Can the class extend another class? | No, it already extends Thread | Yes |
| Job and thread are | Mixed together in one class | Kept separate |
| Can the same job run on several threads? | Not easily | Yes, pass it to several Thread objects |
| Recommended? | Only for simple cases | Yes, in most code |
Easy Way to Remember: Runnable is the job. Thread is the worker who does the job. Keeping them separate means one job can be handed to any worker.
start()
start() creates a new thread and makes it run the run() method there.
- It returns straight away. The calling thread carries on while the new thread works.
- A thread can be started only once. Calling
start()again throws anIllegalThreadStateException.
run()
run() is the method that holds the thread's job. You write it, but you normally do not call it yourself.
If you call run() directly, no new thread is created. The code just runs as an ordinary method call, on the current thread. Read the code, then look at the output below it.
run() runs on the current thread, start() makes a new one
public class Main {
public static void main(String[] args) throws InterruptedException {
Runnable job = () -> System.out.println("Job is running on: " + Thread.currentThread().getName());
Thread t = new Thread(job, "worker");
System.out.println("Calling run():");
t.run();
System.out.println("Calling start():");
t.start();
t.join();
}
}
Output:
Calling run():
Job is running on: main
Calling start():
Job is running on: worker
t.run() ran the job on main, just like any other method. Only t.start() ran it on the new thread called worker.
start() | run() | |
|---|---|---|
| Creates a new thread? | Yes | No |
| Where the job runs | On the new thread | On the thread that called it |
| Returns | Straight away | After the job finishes |
| Can be called twice? | No, throws IllegalThreadStateException | Yes, it is a normal method |
| Who calls it | You | The JVM, after start() |
sleep()
Thread.sleep(milliseconds) pauses the current thread for at least that many milliseconds. 1000 milliseconds is 1 second.
While asleep, the thread is in the TIMED_WAITING state, and other threads can use the CPU.
sleep can throw an InterruptedException, a checked exception. It happens if another thread interrupts the sleeping thread. You must catch it or declare it with throws. Read the code, then look at the output below it.
sleep pauses the current thread
public class Main {
public static void main(String[] args) throws InterruptedException {
String[] lights = {"Red", "Amber", "Green"};
for (String light : lights) {
System.out.println("Traffic light: " + light);
Thread.sleep(200);
}
System.out.println("Cycle complete");
}
}
Output:
Traffic light: Red
Traffic light: Amber
Traffic light: Green
Cycle complete
Each line appears about 0.2 seconds after the one before it. sleep is useful for timers, animations, and waiting a little before trying something again.
Never use sleep to "make sure" another thread has finished. The timing can change from run to run. Use join() for that.
join()
join() makes the current thread wait until another thread has finished.
t.join()waits with no time limit.t.join(500)waits at most 500 milliseconds.
Use join when you need a thread's result before carrying on. Read the code, then look at the output below it.
join waits for two workers before adding up their results
public class Main {
static long firstHalf;
static long secondHalf;
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(() -> firstHalf = sum(1, 500_000));
Thread b = new Thread(() -> secondHalf = sum(500_001, 1_000_000));
a.start();
b.start();
a.join();
b.join();
System.out.println("First half: " + firstHalf);
System.out.println("Second half: " + secondHalf);
System.out.println("Total: " + (firstHalf + secondHalf));
}
static long sum(int from, int to) {
long total = 0;
for (int i = from; i <= to; i++) {
total += i;
}
return total;
}
}
Output:
First half: 125000250000
Second half: 375000250000
Total: 500000500000
The two threads add up different halves of the numbers. On a computer with two or more cores, they can work at the same time.
main calls join() on both before reading their results. Without the joins, main might print 0 because the threads had not finished yet.
Never read a result that another thread is still writing. Wait for it first, with join() or another proper tool. Reading too early gives old or half-finished values, and the bug may only appear on some runs.
Thread Priorities
Every thread has a priority: a number from 1 (lowest) to 10 (highest). A new thread gets the priority of the thread that created it, usually 5.
Thread has three constants:
| Constant | Value |
|---|---|
Thread.MIN_PRIORITY | 1 |
Thread.NORM_PRIORITY | 5 |
Thread.MAX_PRIORITY | 10 |
You read a priority with getPriority() and change it with setPriority(n). Read the code, then look at the output below it.
Read and set thread priorities
public class Main {
public static void main(String[] args) {
Thread background = new Thread(() -> {}, "backup");
Thread urgent = new Thread(() -> {}, "alarm");
Thread normal = new Thread(() -> {}, "worker");
background.setPriority(Thread.MIN_PRIORITY);
urgent.setPriority(Thread.MAX_PRIORITY);
System.out.println("main: " + Thread.currentThread().getPriority());
System.out.println(background.getName() + ": " + background.getPriority());
System.out.println(urgent.getName() + ": " + urgent.getPriority());
System.out.println(normal.getName() + ": " + normal.getPriority());
}
}
Output:
main: 5
backup: 1
alarm: 10
worker: 5
worker was never changed, so it kept the default, 5.
Priorities Are Only Hints
A higher priority suggests to the operating system that a thread should get more CPU time. It does not guarantee it. Different operating systems treat priorities differently, and some mostly ignore them.
So never write a program that is only correct if threads run in priority order. Use join() and the tools in the next lesson to control order.
Setting a priority outside 1 to 10 throws an IllegalArgumentException.
Common mistakes
The following mistakes are common when creating and starting threads. Each row shows what you see, why it happens and how to fix it.
| Symptom | Cause | Fix |
|---|---|---|
The job runs on main | run() was called instead of start() | Call start() |
IllegalThreadStateException | start() was called twice | Create a new Thread object |
unreported exception InterruptedException | sleep or join is not handled | Add throws InterruptedException, or catch it |
A result prints as 0 or old data | It was read before the thread finished | Call join() first |
| Output lines change order between runs | Threads ran without waiting for each other | Use join() where order matters |
IllegalArgumentException from setPriority | The priority was outside 1 to 10 | Use a value from 1 to 10 |
What to Remember
- A process is a running program with its own memory. A thread is a path of work inside it, sharing that memory.
- Multithreading keeps programs responsive, uses waiting time, and spreads work across CPU cores.
- A thread moves from
NEWtoRUNNABLE, can wait (BLOCKED,WAITING,TIMED_WAITING), and endsTERMINATED. - Create threads by extending
Thread, or, better, by passing aRunnable(often a lambda) to aThread. start()creates a new thread. Callingrun()directly does not.sleep()pauses the current thread.join()waits for another thread to finish.- Priorities run from 1 to 10. They are hints, not guarantees.
Next: Synchronization