AdvancedReading time: ~11 min

Advanced Concurrency

Thread safety, concurrent collections, deadlock, livelock and starvation

Advanced concurrency in Java is about keeping multi-threaded systems correct, diagnosable, and scalable under real contention. At this level the conversation shifts from one lock or one keyword to thread safety strategy, safe publication, concurrent collections, progress guarantees, and failure modes such as deadlock, livelock, and starvation.

1. Definition

What is advanced concurrency?

It is the design of concurrent systems beyond basic thread creation and single-lock protection.

The central questions become:

  • how is shared state owned?
  • how is state published safely?
  • how do many threads coordinate without collapse?
  • how do you diagnose lack of progress?

Why this matters

Most painful concurrency bugs are not just one missing keyword.

They usually come from:

  • inconsistent ownership
  • hidden shared mutability
  • incorrect publication
  • wrong collection choice
  • lock ordering mistakes
  • progress failures under load

What should a strong answer include?

A strong answer mentions:

  • thread safety strategies
  • immutability
  • safe publication
  • concurrent collections
  • atomic classes
  • LongAdder
  • deadlock
  • livelock
  • starvation
  • backpressure and queue-based design where relevant

2. Core Concepts

2.1 Thread safety strategies

Thread safety can be achieved in several ways:

  • immutability
  • thread confinement
  • safe publication
  • synchronization
  • lock-free or low-lock coordination

Immutability is often the cheapest in mental overhead.

If state cannot change, readers do not need coordination after safe publication.

Thread confinement is also strong.

If only one thread owns a stateful component, many synchronization problems disappear.

2.1.1 Keywords and contracts you should state explicitly here

The key terms for this topic are:

  • thread safety — correct behavior under concurrent use
  • immutability — state cannot change after construction
  • thread confinement — state is used by only one thread
  • safe publication — other threads observe a fully initialized object through a valid publication edge
  • volatile — visibility-oriented field modifier with ordering effects
  • ConcurrentHashMap — concurrent shared map implementation
  • CopyOnWriteArrayList — read-optimized list with copy-on-write mutation cost
  • BlockingQueue — producer-consumer handoff with blocking semantics
  • ConcurrentLinkedQueue — non-blocking queue without built-in backpressure
  • AtomicInteger / AtomicReference — atomic primitives for small state transitions
  • LongAdder — contention-friendly counter for heavy write scenarios
  • deadlock — cyclic waiting with no progress
  • livelock — active threads that still make no useful progress
  • starvation — some work rarely gets access because others dominate resources

2.2 Safe publication

Constructing an object safely is not enough.

Other threads must also observe it through a valid publication edge.

Examples include:

  • final-field semantics after correct construction
  • static initialization
  • volatile write
  • lock release
  • concurrent collection insertion
  • executor handoff

Without safe publication, another thread may see stale references or partially initialized state.

2.3 Concurrent collections

Concurrent collections encode specific coordination strategies.

ConcurrentHashMap is a strong default for shared maps.

CopyOnWriteArrayList is useful for read-mostly structures like listener registries.

BlockingQueue is excellent for producer-consumer pipelines because it expresses:

  • handoff
  • waiting
  • backpressure

ConcurrentLinkedQueue gives non-blocking FIFO behavior, but not backpressure.

Choosing among them is really choosing consistency and contention behavior.

2.4 Progress failures

Correctness is not enough.

You also need progress.

Deadlock means threads wait forever in a cycle.

Livelock means threads remain active but do not complete useful work.

Starvation means some task or thread gets too little access to needed resources.

These are common production incident patterns, not just textbook concepts.

3. Practical Usage

Prefer less sharing first

Before reaching for clever locking, try to reduce shared mutable state.

Often the simplest scalable design uses:

  • immutable values
  • message passing
  • queue-based handoff
  • shard ownership
  • per-thread or per-component confinement

Concurrent collection fit

Use ConcurrentHashMap when many threads share a map.

But remember compound actions still matter.

containsKey() then put() is not atomic.

computeIfAbsent() may be the correct operation.

Use CopyOnWriteArrayList only when writes are rare.

Every write copies the full backing array.

Use BlockingQueue when you need coordination plus pressure.

Atomic primitives with realism

Atomic types are great for:

  • flags
  • small state machines
  • simple counters

Under heavy write contention, LongAdder often scales better than AtomicLong.

But atomics do not magically protect multi-field invariants.

If consistency spans several variables, stronger coordination is still needed.

4. Code Examples

Example 1: `ConcurrentHashMap` plus `LongAdder`

class MetricsRegistry {
    private final ConcurrentHashMap<String, LongAdder> counters = new ConcurrentHashMap<>();

    void increment(String name) {
        counters.computeIfAbsent(name, key -> new LongAdder()).increment();
    }

    long current(String name) {
        LongAdder adder = counters.get(name);
        return adder == null ? 0L : adder.sum();
    }
}

Key points:

  • concurrent map for shared access
  • LongAdder for contention-friendly increments
  • atomic map initialization via computeIfAbsent

Example 2: Queue-based work handoff

class WorkPipeline {
    private final BlockingQueue<Job> queue = new ArrayBlockingQueue<>(1000);

    void submit(Job job) throws InterruptedException {
        queue.put(job);
    }

    void workerLoop() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                Job job = queue.take();
                process(job);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    private void process(Job job) {}
}

Key points:

  • queue encodes waiting and backpressure
  • simpler than hand-rolled coordination in many pipelines

Example 3: Deadlock avoidance by lock ordering or timed locking

boolean transfer(Account from, Account to, long amount,
                 Lock first, Lock second) throws InterruptedException {
    if (!first.tryLock(50, TimeUnit.MILLISECONDS)) {
        return false;
    }
    try {
        if (!second.tryLock(50, TimeUnit.MILLISECONDS)) {
            return false;
        }
        try {
            from.debit(amount);
            to.credit(amount);
            return true;
        } finally {
            second.unlock();
        }
    } finally {
        first.unlock();
    }
}

Key points:

  • time-bounded acquisition can reduce permanent waiting risk
  • ordering and timeout are both design tools

5. Trade-offs

Choice Advantage Cost or risk
Immutability Lowest coordination complexity Requires different data-flow style
Thread confinement Simple reasoning within an ownership boundary Less flexible sharing
ConcurrentHashMap Strong default for shared maps Compound actions still need thought
CopyOnWriteArrayList Excellent for read-mostly workloads Very expensive writes
BlockingQueue Encodes handoff and backpressure Blocking semantics may not fit every case
Atomics Great for small state transitions Weak for multi-field invariants
LongAdder Better counter scaling under contention Less precise instant semantics than single-value CAS intuition

Practical trade-off analysis

Advanced concurrency is mostly about choosing the lowest-complexity design that still scales.

That usually means:

  • reduce sharing first
  • choose the collection that matches the access pattern
  • avoid proving complicated lock protocols if a queue or immutable snapshot can replace them

6. Common Mistakes

Mistake 1: Assuming thread safety comes only from locks

Immutability and confinement can be stronger and simpler.

Correct approach:

  • discuss multiple strategies, not only mutexes

Mistake 2: Ignoring safe publication

A safely built object still needs a valid publication edge.

Correct approach:

  • explain how the reference becomes visible to other threads

Mistake 3: Using `ConcurrentHashMap` but performing non-atomic compound actions around it

The collection is concurrent.

Your multi-step logic may still race.

Correct approach:

  • use atomic methods like computeIfAbsent when needed

Mistake 4: Using `CopyOnWriteArrayList` on write-heavy paths

Every mutation copies the array.

Correct approach:

  • reserve it for read-mostly structures

Mistake 5: Treating atomics as a universal concurrency solution

They work best for narrow state transitions.

Correct approach:

  • use stronger coordination when several fields form one invariant

Mistake 6: Failing to distinguish deadlock, livelock, and starvation

These are different progress failures.

Correct approach:

  • define them precisely and discuss concrete symptoms

7. Deep Dive

7.1 Why safe publication is central

Many developers focus on mutation control.

But publication is equally important.

If a reference escapes incorrectly, other threads may observe a partially initialized object even when the constructor looked fine.

That is why safe publication is a foundational concurrency topic.

7.2 Collection choice is strategy choice

Choosing a concurrent collection is choosing a coordination model.

For example:

  • BlockingQueue chooses pressure-aware handoff
  • ConcurrentLinkedQueue chooses lock-free progress without backpressure
  • CopyOnWriteArrayList chooses read-speed over write-cost

Senior answers make that strategy explicit.

7.3 Progress bugs as production incidents

Deadlock often appears as stuck threads with cyclic waiting.

Livelock appears as activity without throughput.

Starvation appears as unfairness or work classes that rarely complete.

These symptoms matter in diagnostics.

7.4 Advanced concurrency is architecture

At this level the best solution is often not “add another lock”.

It is:

  • change ownership
  • change state shape
  • change handoff mechanism
  • reduce shared mutation

That is the main senior-level shift.

8. Interview Questions

1. What is safe publication?

It is making a fully initialized object visible to other threads through a valid publication edge.

2. Why is immutability powerful in concurrency?

Because it reduces or removes coordination after publication.

3. When is `ConcurrentHashMap` a good default?

When many threads share a map and the access pattern fits its concurrency model.

4. When is `CopyOnWriteArrayList` a good fit?

For read-mostly workloads such as listener registries.

5. Why is `BlockingQueue` often better than a custom lock protocol?

Because it encodes waiting, handoff, and pressure directly.

6. When is `LongAdder` better than `AtomicLong`?

Under high contention for counters.

7. What is the difference between deadlock and livelock?

Deadlock is stuck waiting.

Livelock is active but making no progress.

8. What is starvation?

A thread or class of work rarely gets needed resources because others dominate them.

9. Why do concurrent collections not solve every race condition?

Because compound business logic around them can still be non-atomic.

10. What is the senior-level takeaway?

The best concurrency fix is often a change in ownership and design, not a more complex lock.

9. Glossary

Term Meaning
thread safety Correct behavior under concurrent use
immutability State cannot change after construction
thread confinement State is used by only one thread
safe publication Correct visibility of a fully initialized object
ConcurrentHashMap Concurrent shared map
CopyOnWriteArrayList Read-optimized concurrent list
BlockingQueue Blocking producer-consumer queue
LongAdder Contention-friendly counter
deadlock Cyclic waiting with no progress
starvation Persistent unfair lack of resource access

10. Cheatsheet

  • Prefer less shared mutable state first
  • Immutability and confinement are concurrency strategies too
  • Safe publication is mandatory, not optional
  • ConcurrentHashMap is strong, but compound actions still matter
  • CopyOnWriteArrayList is for read-mostly cases
  • BlockingQueue gives handoff plus backpressure
  • Atomics help small state transitions, not every invariant
  • LongAdder helps heavy-write counters
  • Distinguish deadlock, livelock, and starvation precisely
  • Interjúban nevezd meg a safe publication és progress guarantee fogalmakat

🎮 Games

10 questions