Appearance
Lesson 02 · Executors & Concurrent Utilities
Objectives
After this lesson you will be able to:
- Create and shut down an
ExecutorServiceand submit tasks. - Use concurrent collections and atomic variables instead of manual locks.
- Explain visibility,
synchronized, andvolatile.
ExecutorService
Prefer an executor over raw threads — it manages a pool and a task queue.
java
ExecutorService pool = Executors.newFixedThreadPool(4);
Future<Integer> f = pool.submit(() -> compute());
pool.execute(() -> fireAndForget()); // Runnable, no Future
List<Future<Integer>> all = pool.invokeAll(tasks); // wait for all
pool.shutdown(); // graceful: no new tasks, finish queued
pool.awaitTermination(10, TimeUnit.SECONDS);Exam trap
An executor's threads keep the JVM alive — you must shutdown() (or shutdownNow()) it, or the program won't exit. shutdown() lets queued tasks finish; shutdownNow() attempts to interrupt running ones and returns the not-yet-started tasks. Submitting after shutdown throws RejectedExecutionException.
Concurrent collections
The java.util.concurrent collections are thread-safe without external locking, and their iterators don't throw ConcurrentModificationException:
java
var map = new ConcurrentHashMap<String,Integer>();
map.merge("k", 1, Integer::sum); // atomic counter
var queue = new ConcurrentLinkedQueue<Integer>();
var list = new CopyOnWriteArrayList<String>(); // safe for read-heavy, write-rareAtomics, locks, and visibility
Atomic classes give lock-free, thread-safe updates:
java
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet(); // atomic ++ (i++ is NOT atomic)
counter.compareAndSet(5, 6);For mutual exclusion use synchronized (or a ReentrantLock); for visibility of a flag across threads use volatile:
java
synchronized (lock) { shared++; } // only one thread in the block; also publishes changes
private volatile boolean running; // reads always see the latest writeGotcha
i++ is read-modify-write — three steps, not atomic. Two threads incrementing a plain intlose updates. volatile guarantees visibility but not atomicity (so volatile int i; i++ is still unsafe) — use an AtomicInteger or synchronized for compound actions.
SDET note
Concurrency bugs are timing-dependent and flaky to test. Prefer high-level constructs (ConcurrentHashMap, atomics, executors) over hand-rolled locks, and in tests use invokeAll + Future.get to make completion deterministic rather than sleep.
Key Takeaways
- Use an
ExecutorService(a pool) and alwaysshutdown()it;submitreturns aFuture,executedoesn't. Submitting after shutdown throwsRejectedExecutionException. - Concurrent collections (
ConcurrentHashMap,CopyOnWriteArrayList, …) are thread-safe and fail-safe to iterate. - Atomics give lock-free updates;
i++on a plain (orvolatile)intis not atomic. synchronizedprovides mutual exclusion and visibility;volatileprovides visibility only.
Lesson Quiz
Why might a program with an ExecutorService never exit?
Is volatile int i; i++; thread-safe?
Which collection is thread-safe and won't throw ConcurrentModificationException while iterating?
What does synchronized provide that volatile does not?
What does pool.submit(callable) return?
Next: CompletableFuture. Run the matching code in labs/src/main/java/com/jse21/m07_concurrency/.