What is the Executor interface, and what problem does it solve compared to creating threads directly?
answer
- Single method: execute(Runnable)
- Decouples WHAT (task) from HOW (execution policy)
- Avoids per-task new Thread().start()
- Reuse + bounded + backpressure
- Root of ExecutorService / Executors factory
basics
~20 sExecutor is a tiny interface with one method, execute(Runnable). You hand it a task to run; it decides how and when to run it. This separates submitting work from creating threads, so you don't call new Thread(...).start() everywhere.
solid answer
~40 sExecutor is the base abstraction of the java.util.concurrent task framework. It has a single method, execute(Runnable command), which runs the given task at some point. The key idea is decoupling: callers submit tasks (the 'what') without knowing how they're executed (the 'how') — same thread, a new thread, or a pooled thread. This lets you swap execution policy (thread pool size, queuing, scheduling) without touching task-submission code. Directly using new Thread(r).start() couples your code to one-thread-per-task, which is wasteful (thread creation is expensive) and unbounded (no backpressure, you can exhaust memory). Executor centralizes that policy. In practice you rarely use bare Executor; you use ExecutorService (its richer subinterface) obtained from the Executors factory, but Executor is the conceptual root.
go deeper
Knows Executor has one method, execute(Runnable), and that it lets you run tasks without writing new Thread().start() yourself.
Explains the decoupling of submission from execution policy, why per-task threads are wasteful/unbounded, and how a pool gives reuse and backpressure.
Articulates the contract precisely (no guarantee of which thread or when), relates Executor to ExecutorService/Executors, and discusses execution-policy trade-offs.
Frames Executor as a policy seam enabling system-wide resource governance and backpressure design; reasons about when a direct/caller-runs executor is the right policy and how the abstraction shapes API design.
## The problem: threads are expensive and uncontrolled A **thread** is the unit of independent execution managed by the OS/JVM. The naive way to run a task concurrently in Java is: ```java new Thread(() -> doWork()).start(); ``` This works but has serious downsides: - **Cost:** creating and destroying a thread is expensive (memory for its stack — typically ~512KB–1MB — plus OS scheduling setup). Doing it per task wastes resources. - **Unbounded:** if 10,000 requests arrive, you create 10,000 threads. There is no limit, so you can exhaust memory and crash. There is no **backpressure** (a way to slow down submitters when the system is overloaded). - **Coupling:** the code that *submits* work is tangled with the code that decides *how* it runs. You can't change the strategy (reuse threads? cap the count? schedule later?) without editing every call site. ## The Executor interface Java's answer (added in Java 5, package `java.util.concurrent`) is the **Executor** interface — deliberately minimal: ```java public interface Executor { void execute(Runnable command); } ``` A **Runnable** is just an object with a `run()` method — a task that takes no arguments and returns nothing. `execute` accepts such a task and runs it *at some point in the future*, according to the executor's own **execution policy**. The interface makes **no promise** about *which* thread runs it or *when*. A valid Executor could: - run the task immediately in the calling thread (synchronous); - spawn a brand-new thread per task; - hand it to a reusable **thread pool** (the common case). The whole point is **decoupling task submission from task execution**. The caller says *what* to do; the Executor decides *how*. This is an instance of the producer–consumer pattern: submitters produce tasks, worker threads consume them. ## Why this matters Because execution policy lives behind the interface, you can change it in one place. Want to cap concurrency at 8 threads with a bounded queue that rejects overflow? Configure that on the Executor; the thousands of `execute(task)` call sites don't change. This gives you **resource management**, **reuse** (amortize thread-creation cost across many tasks), and **backpressure** (a bounded queue + rejection policy slows or signals overloaded submitters). ## Where it sits in the hierarchy `Executor` is the root. Its subinterface **ExecutorService** adds lifecycle (shutdown) and result-bearing submission (`submit` returning a `Future`). You almost always program against `ExecutorService`, obtained from the **Executors** factory class (e.g. `Executors.newFixedThreadPool(n)`). But conceptually, `Executor` is the one-method seam that everything else builds on. ## Tiny example ```java Executor executor = Executors.newFixedThreadPool(4); // 4 reusable worker threads executor.execute(() -> System.out.println("ran on " + Thread.currentThread().getName())); ``` The lambda is a `Runnable`; `execute` queues it for one of the four pooled threads. No `new Thread` in sight, and the pool reuses its threads across many tasks.
- Why does execute() take a Runnable and not a Callable?Executor is the minimal base abstraction for fire-and-forget tasks, so it uses Runnable (no result, no checked exception). Result-bearing submission via Callable is added one level up in ExecutorService.submit.
- Could execute() run the task synchronously in the calling thread?Yes. The contract doesn't require asynchrony. A direct executor that just calls command.run() is legal and is sometimes used in tests or as a ThreadPoolExecutor rejection policy (CallerRunsPolicy).
saying these in an interview costs you the question
- Saying Executor guarantees a new/separate thread — it makes no such promise; a valid Executor can run in the caller's thread.
- Thinking execute() returns a result or a Future — it returns void; Future comes from ExecutorService.submit.
- Claiming new Thread per task is fine at scale — it's unbounded and wasteful.
- Confusing Executor (interface) with Executors (factory class).