BackendBench
Coffee

Unravel โ€บ Java concepts โ€บ Concurrency

Concurrency

Runnable, Callable and Future

A Runnable is a task that returns nothing; a Callable<V> returns a value and may throw.

Learn these first

At a glance

Mental model

Leaving a laundry order: the Runnable is "wash these", the Callable is "wash these and tell me the total", and the Future is the numbered ticket.

What happens, in order
  1. 1Runnable r = () -> log("x"); has void run(); Callable<Integer> c = () -> compute(); has V call() throws ExceptionBoth are functional interfaces, so lambdas work.
  2. 2Future<Integer> f = pool.submit(callable); returns at oncef.get() blocks until the result is ready; f.get(2, SECONDS) gives up with TimeoutException; f.isDone() and f.cancel(true) check and stop.
  3. 3An exception thrown inside the task comes out of get() wrapped in ExecutionException: read e.getCause() for the real problem
  4. 4Prefer new Thread(runnable).start() only for quick experimentsHand tasks to an executor instead, so thread creation, limits and shutdown are managed.
Remember
Watch out
Calling run() instead of start()Ignoring the Future, so exceptions disappearCalling get() without timeout in a request threadForgetting that get() wraps exceptions
Example (highlighted lines explain it)
import java.util.concurrent.*;public class Tasks {  static int slowSquare(ExecutorService pool, int n) throws Exception {    Callable<Integer> job = () -> { Thread.sleep(100); return n * n; };    Future<Integer> f = pool.submit(job);    try { return f.get(2, TimeUnit.SECONDS); }    catch (TimeoutException e) { f.cancel(true); throw e; }    catch (ExecutionException e) { throw new IllegalStateException(e.getCause()); }  }  static class Ping implements Runnable { public void run() { System.out.println("ping"); } }}
  1. line 5 uses Future, the core of Runnable, Callable and Future
  2. line 7 uses TimeoutException, the core of Runnable, Callable and Future
  3. line 8 uses ExecutionException, the core of Runnable, Callable and Future
Read the full explanation (about 2 min)

A Runnable is a task that returns nothing; a Callable<V> returns a value and may throw. Submitting one to an executor gives a Future<V>, a ticket you can later redeem for the result.

A mental model

Leaving a laundry order: the Runnable is "wash these", the Callable is "wash these and tell me the total", and the Future is the numbered ticket. You can come back, wait at the counter (get()), or cancel the order.

How it works

Runnable r = () -> log("x"); has void run(); Callable<Integer> c = () -> compute(); has V call() throws Exception. Both are functional interfaces, so lambdas work.

Future<Integer> f = pool.submit(callable); returns at once. f.get() blocks until the result is ready; f.get(2, SECONDS) gives up with TimeoutException; f.isDone() and f.cancel(true) check and stop.

An exception thrown inside the task comes out of get() wrapped in ExecutionException: read e.getCause() for the real problem.

Prefer new Thread(runnable).start() only for quick experiments. Hand tasks to an executor instead, so thread creation, limits and shutdown are managed.

Going deeper

Thread.run() called directly runs on the current thread; only start() creates a new thread. Extending Thread ties the task to the thread, while implementing Runnable separates what to do from who does it.

get() without a timeout can block forever. Always think about timeouts and cancellation; cancel(true) only interrupts, so the task must respond to interruption.

A Future cannot be chained or combined: for pipelines use CompletableFuture. Fire-and-forget submit calls swallow exceptions unless you keep the Future and call get().

Under the hood

ThreadPoolExecutor.submit wraps the callable in a FutureTask, which implements both Runnable and Future. The worker thread runs it, stores the result or exception, and wakes up any threads waiting in get() with a lock-free state machine.

Common mistakes

  • Calling run() instead of start().
  • Ignoring the Future, so exceptions disappear.
  • Calling get() without timeout in a request thread.
  • Forgetting that get() wraps exceptions.

How Unravel recognises it

When you paste code, Unravel looks for these signs of runnable, callable and future. Spotting them yourself is a good habit when you read unfamiliar code:

Part of a learning path

Related concepts

Frequently asked questions

What is Runnable, Callable and Future?

A Runnable is a task that returns nothing; a Callable<V> returns a value and may throw. Submitting one to an executor gives a Future<V>, a ticket you can later redeem for the result.

What are the most common mistakes with Runnable, Callable and Future?

Calling run() instead of start(). Ignoring the Future, so exceptions disappear. Calling get() without timeout in a request thread. Forgetting that get() wraps exceptions.

How can I see Runnable, Callable and Future in real code?

Paste your own Java into Unravel and it will point out where runnable, callable and future appears, explain how it works at the detail level you choose, and draw the flow of the code around it.