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:

  1. What processes and threads are, and how they are different.
  2. Why programs use multithreading.
  3. The states in a thread's life.
  4. How to create threads with the Thread class and the Runnable interface.
  5. The difference between start() and run().
  6. How sleep() pauses a thread, and how join() waits for one to finish.
  7. 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

ProcessThread
What it isA running programA path of work inside a process
MemoryIts own, separate from othersShared with the other threads in its process
Created byThe operating systemThe program, inside its process
Cost to createHigherLower
Talking to othersHard, needs special channelsEasy, through shared objects
If one crashesOther processes keep runningIt can affect the whole process
ExampleYour browserOne 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 applicationWhat the threads do
Web serverOne thread per user request, so many users are served at once
Video gameOne thread draws the screen, another plays sound, another handles input
Download managerSeveral threads download different parts of a file at once
Text editorOne 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.

StateMeaning
NEWCreated with new, but start() has not been called yet
RUNNABLEStarted; running, or ready to run as soon as a core is free
BLOCKEDWaiting to get a lock that another thread holds
WAITINGWaiting with no time limit, for example inside join()
TIMED_WAITINGWaiting for a set time, for example inside sleep(500)
TERMINATEDFinished; 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 ThreadImplement Runnable
Howclass X extends Threadclass X implements Runnable, or a lambda
Can the class extend another class?No, it already extends ThreadYes
Job and thread areMixed together in one classKept separate
Can the same job run on several threads?Not easilyYes, pass it to several Thread objects
Recommended?Only for simple casesYes, 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 an IllegalThreadStateException.

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?YesNo
Where the job runsOn the new threadOn the thread that called it
ReturnsStraight awayAfter the job finishes
Can be called twice?No, throws IllegalThreadStateExceptionYes, it is a normal method
Who calls itYouThe 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:

ConstantValue
Thread.MIN_PRIORITY1
Thread.NORM_PRIORITY5
Thread.MAX_PRIORITY10

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.

SymptomCauseFix
The job runs on mainrun() was called instead of start()Call start()
IllegalThreadStateExceptionstart() was called twiceCreate a new Thread object
unreported exception InterruptedExceptionsleep or join is not handledAdd throws InterruptedException, or catch it
A result prints as 0 or old dataIt was read before the thread finishedCall join() first
Output lines change order between runsThreads ran without waiting for each otherUse join() where order matters
IllegalArgumentException from setPriorityThe priority was outside 1 to 10Use 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 NEW to RUNNABLE, can wait (BLOCKED, WAITING, TIMED_WAITING), and ends TERMINATED.
  • Create threads by extending Thread, or, better, by passing a Runnable (often a lambda) to a Thread.
  • start() creates a new thread. Calling run() 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