Skip to content

Lesson 02 · Executors & Concurrent Utilities

Objectives

After this lesson you will be able to:

  • Create and shut down an ExecutorService and submit tasks.
  • Use concurrent collections and atomic variables instead of manual locks.
  • Explain visibility, synchronized, and volatile.

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-rare

Atomics, 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 write

Gotcha

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 always shutdown() it; submit returns a Future, execute doesn't. Submitting after shutdown throws RejectedExecutionException.
  • Concurrent collections (ConcurrentHashMap, CopyOnWriteArrayList, …) are thread-safe and fail-safe to iterate.
  • Atomics give lock-free updates; i++ on a plain (or volatile) int is not atomic.
  • synchronized provides mutual exclusion and visibility; volatile provides visibility only.

Lesson Quiz

Lesson Quiz · Executors & Concurrent Utilities0 / 5
  1. Why might a program with an ExecutorService never exit?

    • AFutures leak memory
    • BThe pool's threads keep the JVM alive until shutdown() is called
    • Csubmit() blocks
    • DIt always exits
  2. Is volatile int i; i++; thread-safe?

    • AYes — volatile makes it atomic
    • BNo — volatile gives visibility, not atomicity; i++ is read-modify-write
    • CYes — i++ is always atomic
    • DOnly on one core
  3. Which collection is thread-safe and won't throw ConcurrentModificationException while iterating?

    • AArrayList
    • BHashMap
    • CConcurrentHashMap
    • DLinkedList
  4. What does synchronized provide that volatile does not?

    • ANothing
    • BMutual exclusion (atomicity of the block)
    • CFaster reads
    • DDaemon threads
  5. What does pool.submit(callable) return?

    • Avoid
    • Bthe result directly
    • Ca Future
    • Da Thread

Next: CompletableFuture. Run the matching code in labs/src/main/java/com/jse21/m07_concurrency/.