A thread is an independent path of execution within a program. Multithreading lets a Java program do multiple things seemingly at once - essential for responsive applications and servers handling many requests.

Creating a thread

class MyTask extends Thread {
    @Override
    public void run() {
        System.out.println("Running in: " + Thread.currentThread().getName());
    }
}

MyTask task = new MyTask();
task.start(); // starts a new thread - never call run() directly for this

Runnable is usually the better choice

Extending Thread uses up your one allowed superclass. Implementing Runnable instead keeps that slot free and is the more common real-world pattern:

Runnable task = () -> System.out.println("Running via Runnable");
Thread thread = new Thread(task);
thread.start();

The problem: shared state

When two threads read and write the same variable at the same time, you get a race condition - unpredictable, hard-to-reproduce bugs:

class Counter {
    private int count = 0;
    public void increment() { count++; } // NOT thread-safe
    public int getCount() { return count; }
}

count++ looks like one operation but is actually three (read, add, write) - two threads can interleave those steps and lose an update.

synchronized fixes it

class Counter {
    private int count = 0;
    public synchronized void increment() { count++; } // now thread-safe
    public int getCount() { return count; }
}

synchronized ensures only one thread can execute that method on a given object at a time. It has a performance cost, so use it only where shared state is actually being modified - not as a default on every method.