Codectionary / Developer documentation / Java

Creating Threads (Thread & Runnable)

A thread is an independent path of execution within a program, letting multiple tasks run seemingly simultaneously. Java offers two main ways to create a thread: extending the Thread class directly and overriding its run() method, or implementing the Runnable interface and passing it to a Thread - the Runnable approach is generally preferred, since Java doesn't support multiple inheritance and Runnable keeps your class free to extend something else.

Syntax

Thread t = new Thread(() -> { /* task */ });\nt.start();

Examples

Extending the Thread Class

Creating a thread by subclassing Thread and overriding run().

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println("Thread running: " + i);
        }
    }
}

MyThread thread = new MyThread();
thread.start();  // starts a new thread - do NOT call run() directly

Implementing Runnable

The generally preferred approach - implement Runnable and pass it to a Thread.

class Task implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println("Task running: " + i);
        }
    }
}

Thread thread = new Thread(new Task());
thread.start();

Runnable with a Lambda

Since Runnable is a functional interface, a lambda is often the most concise way to define a thread's task.

Runnable task = () -> {
    for (int i = 1; i <= 3; i++) {
        System.out.println("Lambda thread: " + i);
    }
};

Thread thread = new Thread(task);
thread.start();

// Even more concise, inline:
new Thread(() -> System.out.println("Quick task")).start();

Running Multiple Threads Concurrently

Starting several threads that run independently and roughly in parallel.

Runnable printNumbers = () -> {
    for (int i = 1; i <= 3; i++) System.out.println("Numbers: " + i);
};

Runnable printLetters = () -> {
    for (char c = 'a'; c <= 'c'; c++) System.out.println("Letters: " + c);
};

Thread t1 = new Thread(printNumbers);
Thread t2 = new Thread(printLetters);

t1.start();
t2.start();
// Output order between t1 and t2 is not guaranteed - that's the nature of concurrency

Best practices

  • Prefer implementing Runnable over extending Thread - it keeps your class free to extend another class, since Java only allows single inheritance
  • Always call start() to begin a new thread of execution - calling run() directly just executes the code on the current thread, with no concurrency at all
  • Use an ExecutorService instead of manually creating raw Thread objects for anything beyond simple examples - it manages thread lifecycles far more efficiently
  • Don't assume any particular execution order between separate threads - the operating system's scheduler decides, and it can vary between runs

At a glance

Purpose
General-purpose application development
File extension
.java
Runs in
Java Virtual Machine
Usually used with
JDK and Java libraries

Specifications & further reading

Related Java documentation

synchronized Keyword
The synchronized keyword prevents multiple threads from executing a block of code (or a method) at the same time on the same object, protecting shared, mutable data from race conditions. When one thread enters a synchronized block, it acquires a lock on the specified object; any other thread trying to enter a synchronized block on that same object must wait until the lock is released.
ExecutorService & Thread Pools
ExecutorService, from java.util.concurrent, manages a pool of reusable threads, letting you submit tasks without manually creating and managing individual Thread objects. This avoids the overhead of constantly creating new threads and gives you control over how many run concurrently. Executors provides convenient factory methods for common pool configurations, like a fixed-size pool or a single-threaded executor.
Atomic Variables
Classes like AtomicInteger, AtomicLong, and AtomicBoolean, from java.util.concurrent.atomic, provide thread-safe operations on single variables without needing explicit synchronized blocks. They use low-level, lock-free CPU instructions (compare-and-swap) to guarantee that operations like incrementAndGet() are atomic - performed as a single, uninterruptible step - even when many threads use them concurrently.
Thread Control: sleep, join, interrupt
Java provides several methods for coordinating and controlling thread execution. Thread.sleep() pauses the current thread for a specified duration. join() makes the calling thread wait until another thread finishes before continuing. interrupt() signals a thread that it should stop what it's doing, a cooperative mechanism the target thread must actively check for and respond to.