What is the difference between a process and a thread, and how do you create threads in Java (Thread vs Runnable vs Callable)?

8 minbeginnerthreadprocessrunnablecallable

Quick Answer

A process is an independent, isolated unit of execution with its own memory space; a thread is a lightweight unit of execution within a process, sharing that process's memory with other threads. In Java, you create threads by extending Thread (overriding run()) or implementing Runnable (no return value, passed to a Thread), or implementing Callable (can return a value and throw checked exceptions, typically submitted to an ExecutorService).

Detailed Answer

A process is an independently executing program with its own isolated memory space, file handles, and OS resources — processes don't share memory directly and communicate via IPC (pipes, sockets, shared files). A thread is a unit of execution within a process; all threads in a process share the same heap and static memory, which is what makes concurrent access to shared data a concern (and also what makes inter-thread communication cheap compared to inter-process communication).

Three ways to create a task that runs on a thread:

1. Extend Thread, override run():

class MyThread extends Thread {
    public void run() { System.out.println("running"); }
}
new MyThread().start(); // start(), not run() — run() alone just calls the method normally

2. Implement Runnable (preferred — doesn't burn your one shot at extending a class, and separates "what to run" from "how it runs"):

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

3. Implement Callable<V> — like Runnable but can return a value and throw checked exceptions, typically submitted to an ExecutorService rather than run directly on a raw Thread:

Callable<Integer> task = () -> { return compute(); };
Future<Integer> future = executor.submit(task);
int result = future.get(); // blocks until done, rethrows checked exceptions wrapped

Best practice: prefer Runnable/Callable submitted to an ExecutorService over manually managing raw Thread objects — it decouples task logic from thread lifecycle/pooling and is far easier to tune and reason about at scale.