How does Java handle multi-threading?

Java has built-in, language-level support for multithreading, letting multiple threads of execution run concurrently within a single application through the Thread class, the Runnable interface, and a rich concurrency API for coordination.

Key Points: • A thread can be created either by extending Thread and overriding run(), or by implementing Runnable and passing it to a Thread — the latter is generally preferred since it doesn't tie you to single inheritance. • The JVM and OS scheduler manage thread execution, switching between threads to give the appearance (or, on multi-core systems, reality) of parallel execution. • Java provides synchronization primitives (synchronized, wait/notify) and higher-level tools (java.util.concurrent) for safely coordinating access to shared data. • The Executor Framework (ExecutorService, thread pools) abstracts away manual thread management, making concurrent code easier to write and scale. • Concurrent collections like ConcurrentHashMap and utilities like CountDownLatch or CompletableFuture round out the toolkit for building correct, performant concurrent applications.

Example: A simple background task can be started with new Thread(() -> doWork()).start(), but most real applications instead submit tasks to an ExecutorService-managed pool, which reuses threads instead of creating a new one for every task.

Code Example:

Runnable task = () -> System.out.println("Running in: " + Thread.currentThread().getName());
Thread thread = new Thread(task);
thread.start();

Interview Tip: A concise interview answer is:

"Java supports multithreading natively through the Thread class and Runnable interface, with the JVM and OS scheduler managing actual execution. On top of that, the java.util.concurrent package — thread pools, concurrent collections, atomic variables, and synchronization primitives — makes writing safe, scalable concurrent code much more manageable than raw threads alone."