Java Concurrent Collections: ConcurrentHashMap, BlockingQueue

Java concurrent collections deep dive: ConcurrentHashMap, BlockingQueue, CopyOnWriteArrayList, ConcurrentLinkedQueue, and choosing the right structure.

published: reading time: 20 min read author: GeekWorkBench

Java Concurrent Collections: ConcurrentHashMap, BlockingQueue, and More

The synchronized collections (Collections.synchronizedMap()) serialize all access to your map. That’s fine for low contention, but hammer it with multiple threads and you’ll watch your CPU utilization drop while threads wait in line. Java’s concurrent collections are designed for actual concurrent access. Let’s see where each one belongs.

Introduction

synchronized collections serialize all access. One thread goes, everyone else waits. This works for low contention, but hammer a Collections.synchronizedMap() with multiple threads and you watch CPU utilization drop while threads queue up. Java’s concurrent collections are designed for actual concurrent access, letting readers proceed without blocking and writers proceed with fine-grained locking rather than global serialization.

The concurrent collections hierarchy serves different access patterns. ConcurrentHashMap segments its data into buckets with independent locks, allowing concurrent reads and writes on different buckets with minimal contention. BlockingQueue adds blocking operations for producer-consumer patterns where producers need to wait when the queue is full and consumers need to wait when it is empty. CopyOnWriteArrayList copies its entire array on every write, making writes expensive but reads never block or see partial modifications—ideal for listener lists where reads vastly outnumber writes.

This post covers how ConcurrentHashMap achieves concurrent reads without locking, when to use bounded versus unbounded queues, why SynchronousQueue is a zero-capacity handoff rather than a traditional queue, how LongAdder outperforms AtomicLong under high contention by spreading updates across striped cells, and the common pitfalls that catch engineers: null policy surprises in ConcurrentHashMap, unbounded queues causing OOM, and the thundering herd problem when multiple threads hit computeIfAbsent for the same missing key.

ConcurrentHashMap

Why Not Just Use synchronizedMap?

// This serializes all access - one thread at a time
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
syncMap.put("key", 1); // Only one thread can touch this at a time

// ConcurrentHashMap allows concurrent reads and writes
ConcurrentHashMap<String, Integer> chm = new ConcurrentHashMap<>();
chm.put("key", 1); // Multiple threads can write concurrently

How ConcurrentHashMap Works

ConcurrentHashMap segments its data into buckets, each protected by its own lock. Reads don’t block at all - they see the latest written values thanks to volatile semantics.

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

// Atomic operations - no synchronized needed
map.putIfAbsent("key", 0);  // Put only if absent
map.replace("key", 0, 1);    // Replace only if current value matches
map.computeIfAbsent("key", k -> expensiveComputation()); // Lazy init
map.merge("key", 1, Integer::sum); // Combine values

// Bulk operations - iterate without ConcurrentModificationException
map.forEach(1, (k, v) -> System.out.println(k + "=" + v));
map.search(1, (k, v) -> v > 100 ? k : null); // Search with concurrency

Iteration in ConcurrentHashMap

Unlike regular iterators, ConcurrentHashMap iterators are weakly consistent:

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1);
map.put("b", 2);

// This won't throw ConcurrentModificationException
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    // May or may not see updates that happened during iteration
    // but won't repeat any entries
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

The iterator may reflect changes that occurred during iteration, but it won’t throw CME and won’t revisit entries.

Null Handling

Important: ConcurrentHashMap does not allow null keys or values. This was a deliberate design decision to avoid ambiguity in concurrent contexts.

map.put(null, 1);  // NullPointerException
map.get(null);      // NullPointerException

If you need null support, consider Collections.synchronizedMap() or a plain map with external synchronization.

BlockingQueue

BlockingQueue adds blocking operations for producer-consumer patterns. Producers block when the queue is full, consumers block when empty.

BlockingQueue<Order> queue = new LinkedBlockingQueue<>(1000); // Bounded

// Producer side - blocks if queue is full
public void enqueue(Order order) throws InterruptedException {
    queue.put(order); // Blocks until space available
}

// Consumer side - blocks if queue is empty
public Order dequeue() throws InterruptedException {
    return queue.take(); // Blocks until item available
}

// Non-blocking alternatives
queue.offer(order, 1, TimeUnit.SECONDS); // Wait up to 1 second
queue.poll(1, TimeUnit.SECONDS);          // Wait up to 1 second

Bounded vs Unbounded Queues

// Unbounded - grows as needed, can cause OOM
LinkedBlockingQueue<Order> unbounded = new LinkedBlockingQueue<>();

// Bounded - limits memory usage
LinkedBlockingQueue<Order> bounded = new LinkedBlockingQueue<>(1000);

// Array-based - more predictable performance
ArrayBlockingQueue<Order> arrayQueue = new ArrayBlockingQueue<>(1000);

// Priority-based - keeps elements sorted
PriorityBlockingQueue<Order> priorityQueue = new PriorityBlockingQueue<>();

Implementations

ImplementationBoundedNotes
LinkedBlockingQueueOptionalMost common
ArrayBlockingQueueYesFixed size, array-backed
PriorityBlockingQueueNoPriority queue
DelayQueueNoElements with delays
SynchronousQueueYes (capacity 0)Handoff - no storage

SynchronousQueue for Handoffs

SynchronousQueue has no capacity - a put must wait for a take and vice versa:

SynchronousQueue<Task> handoff = new SynchronousQueue<>();

// Producer
new Thread(() -> {
    try {
        // Blocks until consumer takes
        handoff.put(task);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}).start();

// Consumer
Task task = handoff.take(); // Blocks until producer puts

Use this when you want direct handoff between threads without buffering.

CopyOnWriteArrayList

CopyOnWriteArrayList creates a fresh copy on every write. Reads are lock-free and never see partial modifications.

CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();

// Readers - never block, never see partial writes
public void notifyListeners() {
    for (Listener listener : listeners) {
        listener.onEvent(); // Safe iteration
    }
}

// Writers - expensive, but no readers ever blocked
public void addListener(Listener listener) {
    listeners.add(listener); // New copy created
}

When to Use CopyOnWriteArrayList

Good for:

  • Listener lists where reads vastly outnumber writes
  • Cached configurations read frequently but updated rarely
  • Situations where iteration must not throw CME

Bad for:

  • Write-heavy workloads (every write copies the entire array)
  • Large lists (copying costs grow with size)
  • When you need to modify during iteration (use iterator.remove())

When NOT to Use Concurrent Collections

Concurrent collections are not always the right call, and using them reflexively creates problems. If your collection is only ever accessed from one thread — which is common in simpler applications or narrow scopes — a plain ArrayList, HashMap, or ArrayDeque is faster and introduces no concurrency overhead. The lock-free algorithms in ConcurrentHashMap and its siblings perform memory ordering operations on every read and write, and that cost is wasted if no other thread is actually competing.

Snapshot isolation catches people off guard. Concurrent collection iterators are weakly consistent: they may or may not reflect updates that happened during iteration, and they will not throw ConcurrentModificationException — but they also will not give you a point-in-time snapshot. If you need consistent iteration over a collection’s state, you must copy it first (new ArrayList<>(source)). Making decisions based on weakly consistent iteration is how subtle correctness bugs creep in.

ConcurrentHashMap gets overused as a catch-all cache or counter. The computeIfAbsent method looks handy for building caches, but if your loader blocks or does expensive computation, you create a thundering herd where dozens of threads all compute the same key at once. Use LoadingCache from Guava or Caffeine instead. And if you are using ConcurrentHashMap as a counter with incrementAndGet() under heavy contention, you are likely slower than LongAdder — it spreads contention across internal cells instead of all hitting the same CAS. See Java Atomics and VarHandle for lock-free alternatives that handle high-contention scenarios better.

ConcurrentLinkedQueue and Deque

Lock-free linked structures for high throughput:

ConcurrentLinkedQueue<Task> queue = new ConcurrentLinkedQueue<>();
ConcurrentLinkedDeque<Task> deque = new ConcurrentLinkedDeque<>();

// Queue operations
queue.offer(task);      // Add to tail
Task task = queue.poll(); // Remove from head

// Deque operations - can operate on both ends
deque.offerFirst(task);
deque.offerLast(task);
Task first = deque.pollFirst();
Task last = deque.pollLast();

Choosing a Concurrent Collection

// Need a map with concurrent access?
// - High read, low write: ConcurrentHashMap
// - Need ordering: ConcurrentSkipListMap

// Need a queue for producer-consumer?
// - Bounded: LinkedBlockingQueue or ArrayBlockingQueue
// - Handoff (no buffer): SynchronousQueue
// - Priority-based: PriorityBlockingQueue

// Need a list with safe iteration?
// - Read-heavy, few updates: CopyOnWriteArrayList
// - Otherwise: ConcurrentLinkedQueue for queue semantics

// Need a set?
// - ConcurrentHashMap.newKeySet() - most efficient
// - ConcurrentSkipListSet - if ordering needed

Architecture Diagram

ConcurrentMap

graph TD
    subgraph ConcurrentMap
        CM1[ConcurrentHashMap] --> CM2[Segment-based locking]
        CM3[ConcurrentSkipListMap] --> CM4[Red-black tree, lock-free]
    end

BlockingQueue

graph TD
    subgraph BlockingQueue
        BQ1[LinkedBlockingQueue] --> BQ5[Bounded/unbounded linked]
        BQ2[ArrayBlockingQueue] --> BQ6[Fixed array, bounded]
        BQ3[PriorityBlockingQueue] --> BQ7[Heap, priority ordered]
        BQ4[SynchronousQueue] --> BQ8[No storage, direct handoff]
    end

CopyOnWrite

graph TD
    subgraph CopyOnWrite
        COW1[CopyOnWriteArrayList] --> COW2[Full copy on write]
    end

LockFree

graph TD
    subgraph LockFree
        LF1[ConcurrentLinkedQueue] --> LF3[CAS operations]
        LF2[ConcurrentLinkedDeque] --> LF3
    end

Production Failure Scenarios

Scenario 1: Memory Explosion with CopyOnWriteArrayList

// BROKEN - writing frequently causes memory churn
CopyOnWriteArrayList<LogMessage> logs = new CopyOnWriteArrayList<>();
executor.scheduleAtFixedRate(() -> {
    logs.add(new LogMessage("periodic update")); // Copies entire list every second!
}, 1, 1, TimeUnit.SECONDS);

// FIXED - use a queue or concurrent collection
ConcurrentLinkedQueue<LogMessage> logs = new ConcurrentLinkedQueue<>();

Scenario 2: BlockingQueue Without Capacity

// BROKEN - unbounded queue can grow without limit
BlockingQueue<Task> queue = new LinkedBlockingQueue<>();
// If producers outpace consumers, this grows until OOM

// FIXED - always set reasonable capacity
BlockingQueue<Task> queue = new LinkedBlockingQueue<>(1000);

Scenario 3: ConcurrentHashMap and null Values

// BROKEN - NPE will occur
ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
cache.putIfAbsent("user", null); // NPE

// FIXED - use a different marker value
cache.putIfAbsent("user", NULL_PLACEHOLDER);

Trade-off Table

CollectionReadsWritesMemoryUse When
ConcurrentHashMapLock-freeLock per segmentLowGeneral concurrent map
HashtableLock wholeLock wholeMediumLegacy compatibility
synchronizedMapLock wholeLock wholeMediumNeed null keys
CopyOnWriteArrayListLock-freeExpensive copyHigh per writeRead-heavy, few writes
LinkedBlockingQueueBlockingBlockingModerateProducer-consumer
ArrayBlockingQueueBlockingBlockingFixedBounded producer-consumer
SynchronousQueueBlockingBlockingNoneDirect handoff

Implementation Snippets

Producer-Consumer with BlockingQueue

public class OrderProcessor {
    private final BlockingQueue<Order> orders = new LinkedBlockingQueue<>(500);
    private final ExecutorService consumerPool;

    public OrderProcessor(int consumerCount) {
        consumerPool = Executors.newFixedThreadPool(consumerCount);
        for (int i = 0; i < consumerCount; i++) {
            consumerPool.submit(this::processOrders);
        }
    }

    public void submitOrder(Order order) throws InterruptedException {
        orders.put(order);
    }

    private void processOrders() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                Order order = orders.take();
                process(order);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }

    public void shutdown() {
        consumerPool.shutdownNow();
    }
}

Cache with ConcurrentHashMap

public class DataCache<K, V> {
    private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
    private final Function<K, V> loader;

    public DataCache(Function<K, V> loader) {
        this.loader = loader;
    }

    public V get(K key) {
        return cache.computeIfAbsent(key, loader);
    }

    public void invalidate(K key) {
        cache.remove(key);
    }

    public void clear() {
        cache.clear();
    }
}

Observability Checklist

  • Do you know which collections allow null values?
  • Are your bounded queues actually bounded?
  • Can you detect queue saturation in monitoring?
  • Are you using CopyOnWriteArrayList for read-heavy workloads only?
  • Do you have appropriate queue capacity for backpressure?
  • Are consumers faster than producers on average?

Security Notes

Collections can expose data if not properly secured:

  • ConcurrentHashMap doesn’t prevent unauthorized read access within the same JVM
  • Use defensive copies when passing collections to untrusted code
  • Consider immutability wrappers for shared data: Collections.unmodifiableMap()

Common Pitfalls / Anti-Patterns

  1. Using synchronized collections under load: They’ll serialize access and kill throughput
  2. CopyOnWriteArrayList for write-heavy use: Every write copies the entire array
  3. Unbounded queues: Can cause OutOfMemoryError
  4. Assuming iteration is point-in-time: In concurrent collections, it’s weakly consistent
  5. Ignoring null policies: ConcurrentHashMap rejects nulls
  6. Queue capacity mis-tuning: Too small causes blocking, too large wastes memory

Quick Recap Checklist

  • ConcurrentHashMap uses segment-based locking, allows concurrent reads/writes
  • BlockingQueue blocks producers on full queue, consumers on empty
  • CopyOnWriteArrayList copies entire array on write, great for read-heavy
  • SynchronousQueue is zero-capacity handoff, not a queue
  • Bounded queues provide backpressure
  • ConcurrentHashMap doesn’t allow null keys/values
  • Iteration is weakly consistent in concurrent collections

Interview Questions

1. How does ConcurrentHashMap achieve concurrency without locking reads?

ConcurrentHashMap divides its data into segments (default 16), each with its own lock. Reads access volatile fields directly without locking. Writes only lock the specific segment containing the target bucket.

Since Java 8, it also uses CAS operations for some updates and reduced locking. The key insight is that different segments can be accessed concurrently, so threads don't wait for unrelated buckets.

2. When would you use CopyOnWriteArrayList over regular ArrayList?

Use CopyOnWriteArrayList when reads vastly outnumber writes and you need to iterate without ConcurrentModificationException. Classic use case: listener/callback lists that are read frequently but modified rarely.

Don't use it if writes are frequent - every add() and remove() copies the entire array, which gets expensive fast. For most concurrent scenarios, ConcurrentLinkedQueue or ConcurrentHashMap.newKeySet() are better choices.

3. What is the difference between BlockingQueue and ConcurrentLinkedQueue?

BlockingQueue operations block: put() blocks when full, take() blocks when empty. This makes it ideal for producer-consumer patterns where consumers need to wait for work.

ConcurrentLinkedQueue uses lock-free CAS operations, never blocks, and is generally higher throughput. Use it when you want non-blocking behavior and can handle the queue being empty or full yourself.

4. Why does ConcurrentHashMap not allow null keys or values?

The decision was made to eliminate ambiguity in concurrent contexts. If map.get(key) returns null, you can't tell if the key wasn't present or if the value was actually null. With concurrent access, another thread might have just removed the value.

In single-threaded maps you can check with containsKey(), but in concurrent scenarios this check-then-get is itself racy. Forcing non-null values eliminates this ambiguity.

5. How do you choose between ArrayBlockingQueue and LinkedBlockingQueue?

ArrayBlockingQueue has a fixed-size array and better memory locality since elements are contiguous. It has lower memory overhead per element.

LinkedBlockingQueue has optional capacity (unbounded by default) and uses separate nodes with next pointers. It can handle more elements without resizing but has higher per-element memory overhead and worse cache locality.

For bounded producer-consumer queues with predictable throughput, ArrayBlockingQueue is often better. For unbounded or very large queues, LinkedBlockingQueue's dynamic nature helps.

6. What is the thundering herd problem with ConcurrentHashMap.computeIfAbsent?

When multiple threads hit computeIfAbsent simultaneously for the same missing key, they all may invoke the loader/computer function simultaneously—wasted work that a single thread could have done.

The loader returns the same result for the same key, so only one thread's work is useful. Under high concurrency for the same key, this causes CPU contention, memory pressure from intermediate objects, and potential performance collapse. Use a cache like Caffeine or Guava's LoadingCache, which handles this via chunking or locking per key.

7. When should you use SynchronousQueue over other BlockingQueue implementations?

SynchronousQueue has capacity zero—it doesn't actually store elements. A put() must wait for a take(), and vice versa. This produces direct handoff between threads with no buffering.

Use it when you want true handoff semantics: producer and consumer must meet at the same time. This is useful for thread pools that execute tasks submitted by callers directly (Executors.newCachedThreadPool() uses a SynchronousQueue internally), or for heavyweight tasks where buffering would waste memory.

8. Why does CopyOnWriteArrayList have a forEach that doesn't throw ConcurrentModificationException?

CopyOnWriteArrayList creates a fresh copy of its internal array on every write operation (add, remove). Iterators hold a reference to the immutable snapshot taken at creation time.

Since the snapshot never changes, the iterator never needs to modify it — reads proceed against a stable snapshot without synchronization. This avoids ConcurrentModificationException, but means iterators may reflect stale state if the list was modified after creation.

9. What is DelayQueue and when is it useful?

DelayQueue holds elements that implement the Delayed interface—each element has a getDelay(TimeUnit) method returning its remaining delay. An element only becomes available for removal (via take() or poll()) when its delay has expired.

Useful for scheduling tasks that should run after a fixed delay, such as retry with backoff, session expiration, or cached items with TTL. Elements with negative or zero delay are at the head and will be retrieved immediately.

10. What does it mean that concurrent collection iterators are "weakly consistent"?

Weakly consistent means the iterator may or may not reflect updates that occurred after iteration began, but it will never throw ConcurrentModificationException and will not repeat any elements.

In ConcurrentHashMap specifically, the iterator can tolerate concurrent modification, may proceed with stale reads, and won't block waiting for ongoing writes. This is a deliberate design trade-off for throughput—no locking on reads—which differs from the fail-fast iterators of synchronized collections.

11. How does ConcurrentHashMap's segment-based locking scale under high read/write contention?

ConcurrentHashMap segments are independent locks, each guarding a subset of buckets. Readers access volatile fields without locking. Writers lock only the specific segment containing their target bucket. Scaling depends on access patterns: if different threads access different segments, they proceed concurrently. Under high contention on a single bucket, you get serialization. The default 16 segments creates a floor on concurrent access (maximum 16 concurrent writers before lock contention). Java 8+ uses CAS operations reducing locking granularity to single bucket operations and improved scalability.

12. What is the stale data problem in concurrent collections and how does drainTo() address it?

Stale data refers to items that have been polled from a queue but represent state from before the poll operation began. In concurrent queues, poll() atomically removes and returns an element, but between the check for empty and the removal, state could change. drainTo() removes multiple elements in a single operation compared to repeated poll() calls, reducing the window where stale reads could occur in the consuming code. For queue monitoring, drainTo() batches removals which is more efficient than individual polls in a loop.

13. What is ConcurrentSkipListMap and how does it differ from ConcurrentHashMap?

ConcurrentSkipListMap is a concurrent, lock-free sorted map based on skiplists. ConcurrentHashMap is an unsorted hash map with segment-based locking. Use ConcurrentSkipListMap when you need key ordering (sorted by key) or range operations (subMap, headMap, tailMap). ConcurrentSkipListMap provides log-time average performance for containsKey, get, put, remove, size operations. ConcurrentHashMap provides constant-time average performance but no ordering guarantees.

14. Why does ConcurrentLinkedQueue use a lock-free algorithm and what are the implications?

ConcurrentLinkedQueue uses optimistic linked nodes with CAS for enqueue and dequeue operations. This avoids locks and blocking under contention, providing better throughput thanBlockingQueue for many producer-consumer scenarios. The trade-off is that operations may retry under contention (the ABA problem applies), and iteration is weakly consistent. The queue never blocks, so offer() returns immediately even when the queue appears full - a design choice that differs fromLinkedBlockingQueue semantics.

15. How does ArrayBlockingQueue differ from LinkedBlockingQueue in memory and performance characteristics?

ArrayBlockingQueue uses a fixed-size circular array with a single lock for both producers and consumers, resulting in lower memory overhead per element but higher contention. LinkedBlockingQueue uses separate nodes with next pointers, higher per-element memory overhead, but supports optional capacity (unbounded) and has better cache locality per element. ArrayBlockingQueue's fairness option ensures waiting threads are served in FIFO order; LinkedBlockingQueue uses an Linked list with independent nodes and may have more unpredictable ordering under heavy load.

16. What is the difference between ConcurrentHashMap.putIfAbsent() and computeIfAbsent()?

putIfAbsent(key, value) inserts the value only if key is absent and returns the existing value (or null if absent). It always performs a no-op read and conditional write even when absent. computeIfAbsent(key, mappingFunction) lazily computes the value only if key is absent using the mappingFunction, and returns the result. If the mappingFunction is expensive, computeIfAbsent avoids that cost when the key is already present. However, computeIfAbsent with a slow loader can cause thundering herd problems under concurrent access for the same key.

17. How does BlockingQueue's put() and offer() differ in blocking behavior?

put() blocks the calling thread indefinitely until space is available. offer() with timeout blocks for the specified duration before returning false. For bounded queues, use offer(timeout) to avoid indefinite blocking in producer threads. For producer-consumer patterns where blocking is expected, put() is simpler. offer() returns immediately with false for non-blocking producers that should fail fast rather than wait.

18. What is the implication of CopyOnWriteArrayList's copy-on-write semantics for memory usage under frequent writes?

CopyOnWriteArrayList copies its entire array on every write operation (add, remove, set). Under frequent writes, this causes constant full-array copying and garbage collection churn. For many writes per second, memory usage can spike dramatically. The copy overhead grows with list size. For write-heavy scenarios, CopyOnWriteArrayList is contraindicated - use concurrent collections like ConcurrentLinkedQueue or synchronized alternatives without copying. The read optimization only pays off when reads vastly outnumber writes (e.g., listener lists, configuration).

19. How does ConcurrentHashMap handle size() and isEmpty() correctly under concurrent modifications?

size() in ConcurrentHashMap counts elements across all segments, requiring segment traversal and synchronization. This is O(segment count) and may be slightly stale. isEmpty() similarly reports a point-in-time assessment. Both operations have weak consistency semantics and may not reflect recent concurrent inserts/removes. For accurate size in highly concurrent scenarios, consider external counters, or accept approximate counts. The weak consistency is by design because locking all segments to compute exact size would be prohibitively expensive.

20. What is the appropriate use case for ConcurrentLinkedDeque vs ConcurrentLinkedQueue?

ConcurrentLinkedQueue is a single-ended queue (FIFO) supporting offer(), poll(), peek(). ConcurrentLinkedDeque is a double-ended queue allowing operations at both ends: offerFirst(), offerLast(), pollFirst(), pollLast(). Use ConcurrentLinkedQueue for simple producer-consumer with a single consumer-producer pair. Use ConcurrentLinkedDeque when you need LIFO (stack) semantics, multiple producers and consumers on different ends, or work-stealing patterns. Both are lock-free and non-blocking.

Further Reading

Conclusion

You now understand Java’s concurrent collection hierarchy from ConcurrentHashMap to CopyOnWriteArrayList. Apply the right collection for your access pattern: segment-locked maps for general concurrent access, blocking queues for producer-consumer workflows, and copy-on-write lists for read-heavy listener patterns. Always use bounded queues to introduce backpressure, and remember that ConcurrentHashMap does not allow null keys or values. Continue with Java Atomics and VarHandle to explore lock-free alternatives to synchronized collections.

Category

Related Posts

Java Atomics and VarHandle: Low-Level Concurrency

Understanding Java atomic operations: AtomicInteger, AtomicReference, VarHandle, compareAndSet, atomics vs locks, and lock-free programming patterns.

#java #jvm #concurrency

Java Memory Model: Happens-Before, Volatile, and Final Fields

Understanding happens-before guarantees, volatile field semantics, and final field safety in the Java Memory Model for correct concurrent code.

#java #jvm #concurrency

java.util.concurrent: Thread Pools, ForkJoinPool, Synchronizers

Master Java's java.util.concurrent: Executors, ForkJoinPool, CountDownLatch, CyclicBarrier, Phaser, and thread pool patterns for high-performance apps.

#java #jvm #concurrency