# Multithreading in Java — Threads, Runnable, synchronized and Executors

Source: https://skills101.in/learn/java/java-multithreading — Skills101 free tutorial (Java Programming). Updated 2026-11-09.

A **thread** is a path of execution inside a program. Multithreading lets one program do several things at once — download a file while the screen stays responsive, or process thousands of requests on a server.

![Stack vs heap: every thread gets its own stack for local variables, but all threads share the same heap — which is why shared objects need care](https://skills101.in/learn-img/java/stack-heap.svg)

**Key idea:** each thread has its **own stack**, but all threads **share the heap**. Sharing objects between threads is where bugs come from.

## 1. Process vs thread

| | Process | Thread |
|---|---|---|
| What | a running program | a path of work inside a process |
| Memory | its own | shares the process's heap |
| Cost to create | high | low |
| Communication | slow (IPC) | fast (shared objects) |

## 2. Creating threads

```java
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            int sum = 0;
            for (int i = 1; i <= 100; i++) sum += i;
            System.out.println(Thread.currentThread().getName() + " sum=" + sum);
        };
        Thread t1 = new Thread(task, "worker-1");
        t1.start();          // start() runs task in a NEW thread
        t1.join();           // wait for it to finish
        System.out.println("main done");
    }
}
```

```text
worker-1 sum=5050
main done
```

- Prefer **implementing `Runnable`** (or a lambda) over extending `Thread` — your class can still extend something else.
- Call **`start()`**, not `run()`. `run()` just runs the code in the current thread.
- `join()` waits for a thread to finish.

## 3. Thread life cycle

**New** → `start()` → **Runnable** → (waiting for a lock / `sleep` / `wait`) **Blocked / Waiting / Timed waiting** → back to Runnable → **Terminated**.

## 4. Race conditions and synchronized

When two threads update the same variable, updates can be lost. `count++` is really three steps (read, add, write).

```java
public class Main {
    static int unsafe = 0;
    static int safe = 0;
    static synchronized void incSafe() { safe++; }

    public static void main(String[] args) throws InterruptedException {
        Runnable job = () -> {
            for (int i = 0; i < 100_000; i++) {
                unsafe++;        // not protected
                incSafe();       // one thread at a time
            }
        };
        Thread a = new Thread(job), b = new Thread(job);
        a.start(); b.start();
        a.join(); b.join();
        System.out.println("safe = " + safe);
        System.out.println("unsafe is " + (unsafe == 200_000 ? "lucky this time" : "less than 200000 (lost updates!)"));
    }
}
```

`safe` is always **200000**. `unsafe` is usually **less** — the exact number changes every run, which is exactly the problem.

- `synchronized` lets only **one thread at a time** into the method (it locks the object/class).
- For simple counters, **`AtomicInteger`** is faster: `counter.incrementAndGet()`.

## 5. ExecutorService: the modern way

Creating threads by hand is rare in real code. Use a **thread pool**:

```java
import java.util.*;
import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        List<Future<Integer>> results = new ArrayList<>();
        for (int n = 1; n <= 5; n++) {
            final int x = n;
            results.add(pool.submit(() -> x * x));     // a Callable returns a value
        }
        int total = 0;
        for (Future<Integer> f : results) total += f.get();   // waits for each result
        pool.shutdown();
        System.out.println("sum of squares = " + total);
    }
}
```

```text
sum of squares = 55
```

## 6. Deadlock

Thread A holds lock 1 and waits for lock 2; thread B holds lock 2 and waits for lock 1. Both wait forever.
**Prevent it** by always taking locks in the **same order**, holding them briefly, or using timeouts (`tryLock`).

## Interview quick answers

- **start() vs run()?** start() creates a new thread; run() is a normal method call.
- **sleep() vs wait()?** sleep pauses without releasing locks; wait releases the lock and waits for notify().
- **volatile?** makes a variable's latest value visible to all threads (no atomicity).
- **Thread-safe collections?** `ConcurrentHashMap`, `CopyOnWriteArrayList`, `BlockingQueue`.

## Common mistakes

- Calling `run()` instead of `start()`.
- Sharing mutable data without synchronization.
- Forgetting `shutdown()` on an ExecutorService (the program may not exit).

## Quick recap

- Threads share the heap, have their own stacks.
- Runnable/lambda + `start()`; `join()` to wait.
- Protect shared data with `synchronized` or atomic classes.
- Use ExecutorService thread pools; avoid deadlock with consistent lock order.


Practice quiz and free certificate: https://skills101.in/learn/java/java-multithreading