Thread Pools & ExecutorService
Why pools beat raw threads, the ExecutorService, submitting Callable tasks, retrieving results with Future, pool sizing, and graceful shutdown.
Assumes you know
After this you should be able to
- Explain the core idea in Thread Pools & ExecutorService
- Apply Thread Pools & ExecutorService in a representative exercise
Everything I need to run tasks asynchronously in Java without manually juggling threads — pooling, submitting work, getting results back, and choosing the right tool for the job.
A request comes in, you do the obvious thing: new Thread(task).start(). It works. Then traffic spikes — 10,000 requests — and you’ve just asked the OS for 10,000 threads.
Each one carries roughly a megabyte of stack and a slice of the scheduler’s attention. The machine spends more time switching between threads than running them, memory balloons, and the process falls over. The cost wasn’t the work — it was creating and destroying a thread for every task.
A thread pool hires a small fixed crew once and feeds them tasks from a queue. This note is how you run work that way in Java: the Executor framework, Runnable vs Callable, the Future you collect results from, and the shutdown you must not forget.
Detailed Study Notes#
1The problem thread pools solve#
Picture an online food-ordering system. Every order has to be processed, sent to the kitchen, and have its status pushed back to the customer. The naive approach is: a new order arrives, so I create a fresh worker (thread) to handle it, and when it's done that worker disappears. This breaks down fast:
- Slow processing — creating a thread isn't free; the customer waits while the "worker is hired."
- Overload at peak — a lunch rush spawns thousands of threads at once, the system thrashes, everyone's order is late.
- Management chaos — constantly fluctuating worker counts are hard to reason about, schedule, and tune.
The fix is a team of trained workers on standby. They're already there, ready to grab the next order the instant it lands. In software terms that standby crew is a thread pool: a fixed, reusable set of threads that pick tasks off a queue.
A restaurant doesn't hire a new chef per plate. It keeps a kitchen brigade. Orders queue on the rail, free cooks grab the next ticket. The pool is that brigade.
Thread creation costs real time and memory (stack, OS scheduling structures). At scale, reuse is the entire game — it converts an unbounded, spiky cost into a bounded, predictable one.
+Unbounded thread creation is a classic outage cause: each Java thread reserves ~512KB–1MB of stack by default, so tens of thousands of threads can exhaust memory and crash the JVM long before the CPU is the bottleneck.
2Thread pool in Java + the Executor framework#
Executors factory for one.A thread pool is a well-organized team of workers that avoids constant creation/destruction and efficiently handles a stream of tasks. In Java this lives in the Executor framework (package java.util.concurrent, since Java 5). Two pieces matter:
Executors— a utility/factory class with static methods that hand you ready-made pools.ThreadPoolExecutor— the concrete implementation under the hood; use it directly when you need full control.
The interfaces nest: Executor (just execute(Runnable)) ⊂ ExecutorService (adds submit, shutdown, invokeAll/Any) ⊂ ScheduledExecutorService (adds delayed/periodic scheduling). You program to the interface, the factory picks the implementation.
The framework cleanly separates what work to do (the task you submit) from how/when it runs (the pool's policy). You write tasks; the executor owns threading.
3Types of thread pools#
ThreadPoolExecutor when none fit.| Factory method | Behaviour | Best for |
|---|---|---|
newFixedThreadPool(n) | Exactly n threads. If all busy, new tasks wait in an (unbounded) queue. | Steady, predictable load; capping concurrency. |
newCachedThreadPool() | Creates threads as needed, reuses idle ones, +retires threads idle >60s. | Lots of short-lived, bursty tasks. |
newSingleThreadExecutor() | One thread. Tasks run sequentially, in order. | Strict ordering; only one task at a time. |
newScheduledThreadPool(n) | Runs tasks after a delay or repeatedly on a schedule. | Timers, polling, cron-like jobs. |
new ThreadPoolExecutor(...) | Custom core/max size, queue type, keep-alive, rejection policy. | When you need real control. |
Example construction lines look like:
ExecutorService fixed = Executors.newFixedThreadPool(4);
ExecutorService cached = Executors.newCachedThreadPool();
ExecutorService single = Executors.newSingleThreadExecutor();
ScheduledExecutorService sched = Executors.newScheduledThreadPool(4);
+newFixedThreadPool and newSingleThreadExecutor use an unbounded LinkedBlockingQueue. Under a flood of tasks the queue grows without limit and you get an OutOfMemoryError. newCachedThreadPool has the opposite risk: it can spawn an unbounded number of threads. In production, many teams build a bounded ThreadPoolExecutor instead — this is a frequent interview "gotcha."
"Why not just always use a cached pool?" → Because it's unbounded; a sudden spike creates a thread per queued task and can take the box down. Fixed pools bound threads but can grow the queue; bounded custom pools bound both.
4Manual thread management vs ExecutorService#
Manual way: you keep a List<Thread>, build a new Thread(task) per job, call .start() on each, then loop calling .join() to wait for them all. You own every detail — and there's no built-in way to get a value back.
ExecutorService way: create a pool once, submit() each task, and call shutdown() when done (or shutdownNow() to force-stop if tasks run too long). The pool handles thread lifecycle, reuse, and queuing. It lets multiple tasks run in parallel across multiple threads.
| Concern | Manual threads | ExecutorService |
|---|---|---|
| Thread lifecycle | You create/start/join each | Pool manages & reuses |
| Launch | thread.start() | executor.submit(task) |
| Wait for completion | for(t: list) t.join(); | Future / awaitTermination() |
| Return a value | Awkward (Runnable is void) | Natural via Callable + Future |
| Resource reuse | None — new thread each time | Threads pooled & recycled |
| Teardown | Manual | shutdown() |
Manual threading is error-prone (forgotten joins, leaked threads, no result channel). The executor gives you reuse, a result mechanism, and lifecycle control for free.
+An ExecutorService that you never shutdown() keeps non-daemon worker threads alive, so your program won't exit. Always shut it down — ideally in a finally block or try-with-resources (Java 19+ makes ExecutorService AutoCloseable).
5Runnable — fire-and-forget tasks#
Say I want to fetch weather data from an external API on a background thread so the main thread isn't blocked. With a Runnable, I implement void run() and the task executes off-thread — but run() returns nothing and cannot throw checked exceptions. So getting the fetched data back into the main thread, or propagating a fetch failure, is clumsy: I'd have to stash the result in a shared field and coordinate access myself.
Runnable is mailing a letter with no return address. The task gets done somewhere, but nothing comes back to you automatically.
+An exception thrown inside run() doesn't propagate to the submitter — it's swallowed or routed to the thread's uncaught-exception handler. This makes silent failures easy. Callable + Future fixes both problems (result + exception).
6Callable — tasks that return a result#
Callable<V> is a functional interface with a single method V call() that returns a result and can throw checked exceptions. It's the right choice whenever a background task needs to produce a value or report a failure that the calling thread should see. It's used together with ExecutorService and Future: you submit a Callable, you get a Future, and you read the result through the Future.
| Aspect | Runnable | Callable<V> |
|---|---|---|
| Single method | void run() | V call() |
| Returns a value? | No | Yes (V) |
| Throws checked exceptions? | No | Yes |
| Introduced in | Java 1.0 | Java 5 |
submit() gives | Future<?> (resolves to null) | Future<V> |
The original question — "how do I get the result from the background fetch into the main thread?" — is answered by use Callable. It's the canonical way to do request/response style async work.
Be ready to state the three differences crisply: return value, checked exceptions, and method name/signature. Bonus: both are functional interfaces, so both can be written as lambdas.
7Future — a handle on a not-yet-finished result#
The Future<V> interface represents the result of an asynchronous computation. When you submit a task you immediately get a Future back, even though the work may not be done yet. You can then ask the Future to retrieve the result (waiting if necessary), check whether it's finished, or cancel it.
| Method | What it does |
|---|---|
V get() | Blocks until the task completes, then returns the result. +Wraps task errors in ExecutionException. |
V get(timeout, unit) | +Same, but gives up after the timeout with TimeoutException. |
boolean isDone() | True if the task finished — normally, by exception, or by cancellation. |
boolean cancel(mayInterruptIfRunning) | Attempts to cancel; returns whether it succeeded. |
boolean isCancelled() | True if the task was cancelled before completing. |
A coat-check ticket. You hand over your coat (task), get a stub (Future) right away, and redeem it for the coat (get()) when you're ready — and you may have to wait at the counter.
+get() blocks. Calling it right after submit() serializes your tasks and kills the parallelism you set up. Submit all tasks first, then collect results. Also: get() throws checked InterruptedException and ExecutionException — you must handle them.
CompletableFuture (Java 8) is the modern upgrade: non-blocking chaining with thenApply/thenCompose, combining, and callbacks. Plain Future only lets you block on get() or poll isDone().
8Task submission: submit / invokeAll / invokeAny#
These methods control how tasks are queued, executed, and how results come back:
| Method | Input | Behaviour | Returns |
|---|---|---|---|
submit(task) | One Runnable/Callable | Schedules it; returns immediately (non-blocking). | Future<V> |
invokeAll(tasks) | Collection of Callables | Blocks until ALL finish. | List<Future<V>> |
invokeAny(tasks) | Collection of Callables | Returns the FIRST successful result, cancels the rest. | V (the result itself) |
Use submit for individual tasks whose results you'll fetch via Future. Use invokeAll when you need every task's result (a fan-out where all answers matter). Use invokeAny when any one answer is enough and you want the fastest — e.g. querying three mirror servers and taking whichever replies first.
invokeAll = wait for the whole class to finish the exam. invokeAny = a race; the first runner across the line wins and the others are pulled off the track.
Remember the return-type asymmetry: invokeAll gives you a list of Futures, but invokeAny gives you the raw result value (no Future), because there's only one winner.
9Shutting the pool down#
+This is implied by the shutdown() / shutdownNow() calls but worth stating plainly. Pool threads are non-daemon by default, so the JVM won't exit while they exist.
shutdown()— graceful: stops accepting new tasks, but lets already-submitted ones finish.shutdownNow()— forceful: tries to interrupt running tasks and returns the ones never started.awaitTermination(t, unit)— block (up to a timeout) until all tasks have finished after a shutdown call.
The standard, correct teardown is: shutdown() → awaitTermination(...) → if it times out, shutdownNow(). This guarantees clean exit without abandoning in-flight work prematurely.
Connections: Need (1) motivates pools (2–4). To get results out you move Runnable (5) → Callable (6) → Future (7). Submission methods (8) are how Callables enter the pool. Shutdown (9) closes the loop. The whole chain is: create pool → submit Callables → collect via Future → shutdown.
Code Samples#
AManual threads vs ExecutorService#
Same workload (4 print-and-sleep tasks) done both ways. +Reconstructed runnable versions of both snippets.
// ManualThreads.java
import java.util.*;
public class ManualThreads {
public static void main(String[] args) throws InterruptedException {
List<Thread> workers = new ArrayList<>();
for (int i = 1; i <= 4; i++) {
final int id = i;
// Runnable = the task; one new Thread object per task
Thread t = new Thread(() -> System.out.println("Task " + id + " on " + Thread.currentThread().getName()));
workers.add(t);
}
workers.forEach(Thread::start); // must start each thread
for (Thread t : workers) t.join(); // must join each to wait
System.out.println("All manual tasks done");
}
}// PoolDemo.java
import java.util.concurrent.*;
public class PoolDemo {
public static void main(String[] args) throws InterruptedException {
// ONE pool of 4 reusable threads — no per-task Thread objects
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 1; i <= 4; i++) {
final int id = i;
executor.submit(() -> System.out.println("Task " + id + " on " + Thread.currentThread().getName()));
}
executor.shutdown(); // graceful: no new tasks
executor.awaitTermination(5, TimeUnit.SECONDS); // wait for finish
System.out.println("All pool tasks done");
}
}Manual threads are named Thread-0…3; pooled ones are pool-1-thread-1…4. Ordering is non-deterministic because the OS scheduler decides who runs when. Without shutdown(), PoolDemo would never exit — the four pool threads stay alive.
BCreating the pool types#
// PoolTypes.java
import java.util.concurrent.*;
public class PoolTypes {
public static void main(String[] args) {
ExecutorService fixed = Executors.newFixedThreadPool(4); // 4 fixed workers
ExecutorService cached = Executors.newCachedThreadPool(); // grows/shrinks on demand
ExecutorService single = Executors.newSingleThreadExecutor(); // strictly sequential
ScheduledExecutorService sched = Executors.newScheduledThreadPool(2);
single.submit(() -> System.out.println("sequential task"));
// scheduled: run once after a 1-second delay
sched.schedule(() -> System.out.println("ran after 1s delay"), 1, TimeUnit.SECONDS);
// always shut every pool down
fixed.shutdown(); cached.shutdown(); single.shutdown();
sched.schedule(sched::shutdown, 2, TimeUnit.SECONDS); // let the delayed task fire first
}
}+A ScheduledExecutorService ignores plain shutdown() for already-scheduled future tasks oddly in edge cases; here we self-schedule the shutdown so the delayed task isn't cut off. In real code, prefer awaitTermination.
CWeather fetcher — Runnable vs Callable + Future#
+The source references WeatherFetcherRunnable.java and WeatherFetcherCallable.java but doesn't show the bodies. These are faithful, self-contained reconstructions (a fake fetch stands in for the real API call so they run with zero setup).
// WeatherFetcherRunnable.java — the LIMITATION demo
import java.util.concurrent.*;
public class WeatherFetcherRunnable implements Runnable {
private volatile String result; // must stash result in shared state
@Override
public void run() {
// run() returns void → can't hand a value back directly
// run() can't throw checked exceptions → must swallow/wrap
try { Thread.sleep(300); } catch (InterruptedException e) { return; }
result = "Weather: 28C, clear";
}
public String getResult() { return result; }
public static void main(String[] args) throws InterruptedException {
WeatherFetcherRunnable task = new WeatherFetcherRunnable();
Thread t = new Thread(task);
t.start();
t.join(); // must wait, THEN read shared field
System.out.println(task.getResult());
}
}The result travels through a shared volatile field and a manual join(). There's no clean way to propagate a fetch failure to main — exceptions in run() don't reach the caller.
// WeatherFetcherCallable.java — the CLEAN way
import java.util.concurrent.*;
public class WeatherFetcherCallable implements Callable<String> {
@Override
public String call() throws Exception { // returns a value + can throw checked exc
Thread.sleep(300); // pretend network latency
if (Math.random() < 0) throw new Exception("API down"); // could throw freely
return "Weather: 28C, clear"; // result flows back via Future
}
public static void main(String[] args) throws Exception {
ExecutorService ex = Executors.newSingleThreadExecutor();
Future<String> future = ex.submit(new WeatherFetcherCallable()); // non-blocking
System.out.println("Doing other work while fetch runs...");
System.out.println("isDone? " + future.isDone()); // likely false
String weather = future.get(); // BLOCKS until result ready; rethrows as ExecutionException
System.out.println(weather);
ex.shutdown();
}
}Callable returns the value through the Future — no shared field, no manual join. If call() threw, future.get() would raise an ExecutionException wrapping it, so failures reach the caller cleanly.
DApp.java — submit / invokeAll / invokeAny#
// App.java
import java.util.*;
import java.util.concurrent.*;
public class App {
public static void main(String[] args) throws Exception {
ExecutorService ex = Executors.newFixedThreadPool(3);
// three Callables that return after different delays
List<Callable<String>> tasks = List.of(() -> { Thread.sleep(300); return "A (300ms)"; },() -> { Thread.sleep(100); return "B (100ms)"; },() -> { Thread.sleep(200); return "C (200ms)"; }
);
// 1) submit — fire one task, get a Future, don't block
Future<String> f = ex.submit(() -> "single submit result");
System.out.println("submit -> " + f.get());
// 2) invokeAll — blocks until ALL done; returns a Future per task
List<Future<String>> all = ex.invokeAll(tasks);
System.out.print("invokeAll -> ");
for (Future<String> ft : all) System.out.print(ft.get() + " ");
System.out.println();
// 3) invokeAny — returns the FIRST successful result (the fastest, B)
String first = ex.invokeAny(tasks);
System.out.println("invokeAny -> " + first);
ex.shutdown();
}
}invokeAll returns Futures in the input order regardless of finish order, so you get A, B, C. invokeAny returns whichever finishes first — B at 100 ms — and cancels A and C. Note invokeAny returns a bare String, not a Future.
Hands-On Lab#
⚙One-time Setup#
# 1. Check Java is installed (need 17+; any 8+ works for most of this)
java -version
javac -version
# 2. Make a working folder
mkdir -p ~/workspace/java-threads/lab && cd ~/workspace/java-threads/lab
# 3. Compile + run pattern used throughout
# (each file has a public static void main)
javac FileName.java
java FileName+No external libraries needed — everything is in java.util.concurrent. One class per file, filename must match the public class name.
1 · Pool vs no pool, see the thread names#
- Create
NameDemo.javawith anewFixedThreadPool(2). - Submit 6 tasks, each printing
Thread.currentThread().getName(). shutdown()the pool.
Solution
// NameDemo.java
import java.util.concurrent.*;
public class NameDemo {
public static void main(String[] a) {
ExecutorService ex = Executors.newFixedThreadPool(2);
for (int i = 1; i <= 6; i++) {
final int id = i;
ex.submit(() -> System.out.println("task " + id + " -> " + Thread.currentThread().getName()));
}
ex.shutdown();
}
}2 · Make a Callable return a value#
- Write a
Callable<Integer>(lambda) that returns the sum 1..100. - Submit it to a single-thread executor, capture the
Future<Integer>. - Print
future.get(), then shut down.
get() return here?Solution
// SumCallable.java
import java.util.concurrent.*;
public class SumCallable {
public static void main(String[] a) throws Exception {
ExecutorService ex = Executors.newSingleThreadExecutor();
Future<Integer> f = ex.submit(() -> {
int s = 0;
for (int i = 1; i <= 100; i++) s += i;
return s; // Callable can return
});
System.out.println("sum = " + f.get()); // blocks, returns Integer
ex.shutdown();
}
}3 · Feel the blocking trap of get()#
get() too early destroys parallelism.- Create 3 Callables that each
sleep(1000)then return their id. - Version A: submit one,
get()it, submit next,get()it... Time the total. - Version B: submit all 3 first into a list of Futures, then loop
get(). Time it.
Solution
// BlockingTrap.java
import java.util.*;
import java.util.concurrent.*;
public class BlockingTrap {
static Callable<Integer> job(int id) {
return () -> { Thread.sleep(1000); return id; };
}
public static void main(String[] a) throws Exception {
ExecutorService ex = Executors.newFixedThreadPool(3);
long t0 = System.currentTimeMillis(); // --- Version A: get() each time ---
for (int i = 1; i <= 3; i++) ex.submit(job(i)).get();
System.out.println("A took ~" + (System.currentTimeMillis() - t0) + "ms");
long t1 = System.currentTimeMillis(); // --- Version B: submit all, then get ---
List<Future<Integer>> fs = new ArrayList<>();
for (int i = 1; i <= 3; i++) fs.add(ex.submit(job(i)));
for (Future<Integer> f : fs) f.get();
System.out.println("B took ~" + (System.currentTimeMillis() - t1) + "ms");
ex.shutdown();
}
}Lesson: submit everything, then collect. get() is a barrier.
4 · invokeAll — collect every result#
- Build a
List<Callable<String>>of 3 tasks with different sleep times (e.g. 300/100/200 ms). - Call
invokeAll(tasks). - Loop the returned Futures and print each
get().
Solution
// InvokeAllDemo.java
import java.util.*;
import java.util.concurrent.*;
public class InvokeAllDemo {
public static void main(String[] a) throws Exception {
ExecutorService ex = Executors.newFixedThreadPool(3);
List<Callable<String>> tasks = List.of(() -> { Thread.sleep(300); return "slow-300"; },() -> { Thread.sleep(100); return "fast-100"; },() -> { Thread.sleep(200); return "mid-200"; }
);
List<Future<String>> results = ex.invokeAll(tasks); // blocks till ALL done
for (Future<String> f : results) System.out.println(f.get());
ex.shutdown();
}
}5 · invokeAny — race the mirrors#
- Three Callables: fast (100 ms), medium (400 ms), slow (800 ms). Each prints "started" and "finished".
- Call
invokeAny(tasks)and print the single returned value. - Observe which tasks print "finished".
invokeAny return type-wise?Solution
// InvokeAnyDemo.java
import java.util.*;
import java.util.concurrent.*;
public class InvokeAnyDemo {
static Callable<String> mirror(String name, int ms) {
return () -> {
System.out.println(name + " started");
Thread.sleep(ms);
System.out.println(name + " finished"); // cancelled tasks may never reach this
return name + " result";
};
}
public static void main(String[] a) throws Exception {
ExecutorService ex = Executors.newFixedThreadPool(3);
List<Callable<String>> tasks = List.of(mirror("fast", 100), mirror("medium", 400), mirror("slow", 800));
String winner = ex.invokeAny(tasks); // returns String, not Future
System.out.println("WINNER: " + winner);
ex.shutdownNow(); // stop the losers immediately
}
}invokeAny returns the bare value (here a String) and cancels the rest as soon as one succeeds.
6 · Handle a failing task + clean shutdown#
get(), and do a textbook shutdown.- Submit a Callable that throws
new IllegalStateException("boom"). - Wrap
future.get()in try/catch forExecutionException; print the cause. - Shut down with the pattern:
shutdown()→awaitTermination(2s)→shutdownNow()if it times out.
get() throw — IllegalStateException or something wrapping it? What is e.getCause()?Solution
// FailAndShutdown.java
import java.util.concurrent.*;
public class FailAndShutdown {
public static void main(String[] a) throws InterruptedException {
ExecutorService ex = Executors.newFixedThreadPool(2);
Future<String> f = ex.submit(() -> { throw new IllegalStateException("boom"); });
try {
f.get(); // re-throws task error wrapped
} catch (ExecutionException e) {
System.out.println("get() threw: " + e.getClass().getSimpleName());
System.out.println("real cause : " + e.getCause()); // the IllegalStateException
}
ex.shutdown(); // graceful
if (!ex.awaitTermination(2, TimeUnit.SECONDS)) {
ex.shutdownNow(); // forceful fallback
}
System.out.println("clean exit");
}
}The task's exception is always wrapped in ExecutionException; unwrap with getCause(). This is exactly why Callable beats Runnable for error handling.
✓Self-check rubric — how to know you've got it#
- ✓I can explain why a thread pool beats one-thread-per-task in 2 sentences (reuse, bounded cost).
- ✓Given a scenario, I can pick the right pool type (fixed / cached / single / scheduled) and justify it.
- ✓I can state the 3 differences between Runnable and Callable without looking.
- ✓I know that
get()blocks, and why submitting-all-then-getting preserves parallelism (Exercise 3). - ✓I can predict that
invokeAllreturns Futures in input order andinvokeAnyreturns one raw value. - ✓I can write the canonical lifecycle from memory:
create → submit → get → shutdown. - ✓I know a task's exception arrives wrapped in
ExecutionExceptionand how to unwrap it. - ✓I never forget to
shutdown(), and I know why a program hangs if I do.
Key Takeaways#
- Pool, don't spawn. One thread per task is slow and unbounded; a pool reuses a fixed crew of workers.
- Executor framework =
Executorsfactory builds pools;ThreadPoolExecutoris the engine; you code to interfaces. - Four ready pools: fixed (cap concurrency), cached (bursty short tasks), single (strict order), scheduled (delays/repeats).
- ExecutorService > manual threads: submit-and-shutdown replaces create/start/join/cleanup, and gives a result channel.
- Runnable = fire-and-forget, no return, no checked exceptions. Callable<V> = returns a value, can throw.
- Future is the IOU:
get()blocks for the result; alsoisDone,cancel, timedget. - Submission:
submit=one (non-blocking),invokeAll=wait for all,invokeAny=first success wins. - Errors from a task surface as
ExecutionExceptiononget()— unwrap withgetCause(). - Always
shutdown()— or the JVM won't exit. Pattern: shutdown → awaitTermination → shutdownNow.
Recall Card#
| Type | Threads | Use when |
|---|---|---|
newFixedThreadPool(n) | exactly n; extras queue & wait | steady load, cap concurrency |
newCachedThreadPool() | grows on demand, reuses idle, kills idle>60s | many short bursty tasks |
newSingleThreadExecutor() | exactly 1, sequential | strict order, one-at-a-time |
newScheduledThreadPool(n) | n, delayed/periodic | timers, cron-like jobs |
new ThreadPoolExecutor(...) | fully custom | need queue/size/reject control |
Manual new Thread() | ExecutorService | |
|---|---|---|
| create | 1 Thread obj / task | pool reused |
| start | .start() each | .submit(task) |
| wait | .join() loop | Future / awaitTermination |
| result | hard (Runnable=void) | Future from Callable |
| cleanup | you manage | shutdown() |
| Runnable | Callable<V> | |
|---|---|---|
| method | void run() | V call() |
| returns | nothing | a result (V) |
| checked exc | can't throw | can throw |
| since | Java 1.0 | Java 5 |
| with executor | submit→Future<?> (null) | submit→Future<V> |
| Method | Takes | Returns / waits for |
|---|---|---|
submit(task) | 1 task | Future; does NOT block |
invokeAll(tasks) | collection | List<Future>; blocks till ALL done |
invokeAny(tasks) | collection | 1 result, FIRST success; cancels rest |