You are currently viewing Access Collections Concurrently: Two Java Approaches

Access Collections Concurrently: Two Java Approaches

This entry is part 12 of 12 in the series Collections Framework

Introduction

In modern multi-threaded systems, the ability to Access Collections Concurrently is essential for building robust, scalable, and predictable Java applications. Because many business processes rely on parallel tasks, background schedulers, and asynchronous event flows, accessing shared data structures safely becomes a priority.
This article explains how to Access Collections Concurrently using synchronized wrappers, concurrent data structures, and safe iteration techniques.

1. Understanding the Challenges of Concurrent Access

Before learning how to Access Collections Concurrently, developers must understand why standard collection implementations such as ArrayList or HashMap behave unpredictably in multi-threaded environments. Because these structures are not thread-safe, simultaneous writes may corrupt internal states, break invariants, or trigger runtime exceptions.
For example, modifying a non-thread-safe collection while iterating often triggers ConcurrentModificationException, a topic explored in detail in our article Using Iterators Safely. This exception acts as a fail-fast mechanism that signals unsafe interference between threads.

2. Using Synchronized Collections

The first approach to Access Collections Concurrently involves using synchronized wrappers provided by the Collections utility class. These wrappers serialize access so that only one thread interacts with the underlying collection at a time. While this strategy provides an immediate form of thread safety, it may reduce performance under heavy contention because every operation must acquire a single lock.

2.1. Basic thread-safe operations (no explicit synchronized needed)

For most standard operations—such as add(), remove(), get(), or contains()no additional synchronized keyword is required. The wrapper itself ensures atomicity and mutual exclusion:

Example usage:

// Creating a synchronized List for concurrent access
List<String> syncList = Collections.synchronizedList(new ArrayList<>());

syncList.add("Alpha");      // Thread-safe
syncList.remove("Alpha");   // Thread-safe
boolean exists = syncList.contains("Alpha"); // Thread-safe

These basic operations are safe because the wrapper synchronizes internally on the collection’s monitor.

2.2. When manual synchronization is required: Iteration

Iteration is the only operation that requires manually using the synchronized keyword. This ensures that no other thread modifies the collection while it is being traversed. Without manual synchronization, the iteration may produce inconsistent results or throw exceptions.

Example usage:

// Creating a synchronized List for concurrent access
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
syncList.add("Alpha");
syncList.add("Beta");

// Safe iteration requires manual synchronization
synchronized (syncList) {
    for (String value : syncList) {
        System.out.println("Sync item: " + value);
    }
}

This technique provides thread safety but must be used carefully to avoid blocking bottlenecks.

3. Leveraging Concurrent Collections

The java.util.concurrent package provides highly optimized data structures specifically designed to Access Collections Concurrently. Unlike synchronized wrappers—which rely on a single global lock—these concurrent collections use sophisticated techniques such as lock partitioning, fine-grained locking, non-blocking algorithms, and copy-on-write mechanics. As a result, they scale significantly better under multi-threaded workloads.

Below are the main concurrent collections and how they manage locking internally.

3.1 ConcurrentHashMap — Lock Striping and CAS Operations

ConcurrentHashMap is one of the most widely used concurrent data structures. Instead of synchronizing the entire map, it uses:

  • Lock striping (up to Java 7):
    The map is divided into segments, each protected by its own lock. Only updates to the same segment contend.
  • Fine-grained node-level locking (Java 8+):
    Buckets are linked lists or trees. Updates to different buckets proceed independently.
  • CAS (Compare-And-Swap) for reads and uncontended writes:
    Many operations—such as putIfAbsent—avoid locking entirely when contention is low.

These strategies allow multiple threads to update distinct parts of the map concurrently:

        ConcurrentHashMap<String, Integer> scores = new ConcurrentHashMap<>();
        scores.put("Alice", 10);
        scores.put("Bruno", 20);
        scores.forEach((k, v) -> System.out.println(k + " -> " + v));

Parallel reads are completely non-blocking, and writes rarely block unless multiple threads target the same bucket.

3.2 ConcurrentLinkedQueue — Lock-Free, Non-Blocking Algorithm

ConcurrentLinkedQueue implements a wait-free, lock-free queue based on the Michael & Scott algorithm. It uses:

  • Atomic references
  • CAS operations
  • No locks at all

Because CAS avoids blocking, multiple threads can enqueue and dequeue simultaneously:

        Queue<String> queue = new ConcurrentLinkedQueue<>();
        queue.add("Task A");
        queue.add("Task B");
        queue.forEach(System.out::println);

This structure is ideal for producer–consumer pipelines with high throughput.

3.3 CopyOnWriteArrayList — No Locks During Reads

CopyOnWriteArrayList takes an entirely different approach:

Every write operation creates a new copy of the underlying array

  • Reads require no locking and never block
  • Iterators always traverse a snapshot
  • This results in predictable, immutable-like behavior for readers:

This results in predictable, immutable-like behavior for readers:

        CopyOnWriteArrayList<String> colors = new CopyOnWriteArrayList<>();
        colors.add("Red");
        colors.add("Blue");
        colors.forEach(System.out::println);

Because readers never lock, this collection excels in:

  • Event listener lists
  • Configuration snapshots
  • Systems with many reads and few writes

3.4 Weakly Consistent Iterators

All concurrent collections use weakly consistent iterators, which:

This differs from synchronized wrappers, which require external locking to ensure consistent traversal.

4. Summary of Techniques for Managing Collections Concurrently

The table below summarizes the two primary strategies used to Access Collections Concurrently in Java. Each strategy differs in its locking model, scalability characteristics, iteration behavior, and ideal use cases.

StrategyLocking ModelIteration BehaviorPerformance & ScalabilityTypical Use Cases
Synchronized Collections (Collections.synchronizedX)Coarse-grained locking using a single intrinsic monitorRequires manual synchronized block during iteration to prevent inconsistent traversalLimited scalability due to high lock contention; simple but blockingLegacy codebases, low-contention workloads, small collections, straightforward thread safety needs
Concurrent Collections (java.util.concurrent)Fine-grained locking, lock striping, CAS, or fully lock-free algorithms (e.g., ConcurrentLinkedQueue)Weakly consistent iterators that do not throw ConcurrentModificationException; no external locking requiredHighly scalable under heavy concurrency; optimized for throughputHigh-performance systems, multi-threaded pipelines, caches, sorted concurrent structures, read-heavy workloads (via CopyOnWriteArrayList)

Notes:

  • Copy-on-write collections are included under concurrent collections because they implement a specialized concurrency model: no locks during reads and full-array copy during writes.
  • Weakly consistent iterators allow concurrent updates without failing fast, which is essential for scalability.
  • Synchronized collections remain a valid option when simplicity outweighs throughput requirements.

This comparison highlights the trade-offs between simplicity and scalability, helping developers choose the appropriate approach for accessing shared collections safely in multi-threaded applications.

Conclusion

Learning to Access Collections Concurrently enables developers to build safer and more scalable Java applications. By combining synchronized wrappers, concurrent data structures, and iteration-safe patterns, one can mitigate race conditions, improve throughput, and ensure predictable behavior. Reviewing complementary topics—such as Safe Iteration and HashMap Internals—reinforces a holistic understanding of Java collection mechanics.
Ultimately, mastering these techniques allows developers to write production-grade concurrent systems confidently and efficiently.

You can find the complete code for this article on GitHub.

Collections Framework

Sort Collections With Comparable and Comparator

Noel Kamphoa

Experienced software engineer with expertise in Telecom, Payroll, and Banking. Now Senior Software Engineer at Societe Generale Paris.