Copy-on-Write Collections
Master Copy-On-Write semantics for read-heavy concurrent scenarios. Understand when CopyOnWriteArrayList and CopyOnWriteArraySet shine, their memory implications, and why they're perfect for event listener patterns.
๐ At a Glance
| Aspect | Details |
|---|---|
| Topic | CopyOnWriteArrayList, CopyOnWriteArraySet, snapshot iterators |
| Complexity | Intermediate |
| Prerequisites | Part 18 (ConcurrentHashMap), basic concurrency |
| Time to Master | 2-3 hours |
| Interview Frequency | Medium-High (listener patterns, read-heavy scenarios) |
๐ฏ What You'll Learn
After completing this article, you will be able to:
- Understand Copy-On-Write semantics and memory implications
- Use CopyOnWriteArrayList for event listener management
- Know when COW collections are appropriate (and when not)
- Implement thread-safe observer patterns without locks
- Avoid common COW pitfalls like memory spikes
Production Story: The Event Listener That Never Threw
The Incident
ConcurrentModificationException in the event dispatch system. Adding or removing listeners during event dispatch caused random crashes:JAVA(19 lines)CodeLoading syntax highlighter...
Why It Failed
TEXT(7 lines)CodeLoading syntax highlighter...
The CopyOnWriteArrayList Solution
JAVA(19 lines)CodeLoading syntax highlighter...
How It Works
TEXT(19 lines)CodeLoading syntax highlighter...
Performance Trade-off
JAVA(8 lines)CodeLoading syntax highlighter...
Mental Model: The Immutable Photograph
TEXT(29 lines)CodeLoading syntax highlighter...
Interactive Visualization
Watch how CopyOnWrite handles concurrent readers and writers:
Loading visualizer...
Deep Dive: CopyOnWriteArrayList Internals
Core Structure
JAVA(12 lines)CodeLoading syntax highlighter...
Write Operations (Always Copy)
JAVA(35 lines)CodeLoading syntax highlighter...
Read Operations (No Copy, No Lock)
JAVA(35 lines)CodeLoading syntax highlighter...
Deep Dive: CopyOnWriteArraySet
Implementation
JAVA(16 lines)CodeLoading syntax highlighter...
addIfAbsent Pattern
JAVA(23 lines)CodeLoading syntax highlighter...
Deep Dive: When to Use COW Collections
Perfect Use Cases
JAVA(15 lines)CodeLoading syntax highlighter...
Bad Use Cases
JAVA(20 lines)CodeLoading syntax highlighter...
Decision Matrix
TEXT(18 lines)CodeLoading syntax highlighter...
Deep Dive: Common Patterns
Pattern 1: Event Dispatcher
JAVA(28 lines)CodeLoading syntax highlighter...
Pattern 2: Configuration Registry
JAVA(30 lines)CodeLoading syntax highlighter...
Pattern 3: Plugin Manager
JAVA(36 lines)CodeLoading syntax highlighter...
Pattern 4: Rate Limiter Whitelist
JAVA(24 lines)CodeLoading syntax highlighter...
Deep Dive: Memory Implications
Memory Spike During Updates
JAVA(24 lines)CodeLoading syntax highlighter...
Memory Calculation
JAVA(15 lines)CodeLoading syntax highlighter...
Garbage Collection Impact
JAVA(10 lines)CodeLoading syntax highlighter...
โ ๏ธ Common Mistakes
Mistake 1: Using for Large/Frequently Modified Lists
JAVA(14 lines)CodeLoading syntax highlighter...
Mistake 2: Expecting Iterator to See Updates
JAVA(13 lines)CodeLoading syntax highlighter...
Mistake 3: Modifying Through Iterator
JAVA(14 lines)CodeLoading syntax highlighter...
Mistake 4: Using contains() on Large Sets
JAVA(14 lines)CodeLoading syntax highlighter...
Mistake 5: Not Using addAll for Batch Operations
JAVA(8 lines)CodeLoading syntax highlighter...
๐ Debug This
Challenge 1: The Missing Element
JAVA(15 lines)CodeLoading syntax highlighter...
Output:
TEXT(3 lines)CodeLoading syntax highlighter...
size() sees the current list, so it shows 3.Challenge 2: The Duplicate Prevention
JAVA(10 lines)CodeLoading syntax highlighter...
1addIfAbsent which is atomic. Only one "X" will be added regardless of race condition.Challenge 3: The Memory Spike
JAVA(6 lines)CodeLoading syntax highlighter...
add() copies the array. At iteration 999:- Old array: 999 KB (999 references)
- New array: 1000 KB (1000 references)
- Actual byte arrays: 1000 KB
Total peak: ~2MB for array references, plus ~1MB for actual byte arrays = ~3MB
addAll() with pre-built list, peak would be ~1.5MB.๐ป Exercises
Exercise 1: Observable Value
Implement a thread-safe observable value:
JAVA(6 lines)CodeLoading syntax highlighter...
JAVA(31 lines)CodeLoading syntax highlighter...
Exercise 2: Feature Flag Service
Implement a thread-safe feature flag service:
JAVA(6 lines)CodeLoading syntax highlighter...
JAVA(26 lines)CodeLoading syntax highlighter...
Exercise 3: Event Bus with Topics
Implement a simple event bus with topic subscription:
JAVA(5 lines)CodeLoading syntax highlighter...
JAVA(32 lines)CodeLoading syntax highlighter...
๐ค Senior-Level Interview Questions
Question 1: COW vs Synchronized Collections
| Aspect | CopyOnWriteArrayList | synchronizedList |
|---|---|---|
| Read performance | O(1), no lock | O(1), acquires lock |
| Write performance | O(n), copies array | O(1), acquires lock |
| Iteration safety | Never throws CME | Can throw CME |
| Memory | 2x during writes | Constant |
| Best for | Read-heavy, few writes | Balanced read/write |
JAVA(5 lines)CodeLoading syntax highlighter...
Question 2: Snapshot Iterator Semantics
Snapshot iterator captures array reference at creation:
JAVA(15 lines)CodeLoading syntax highlighter...
Question 3: Memory Implications
- Peak memory during write: 2x normal (old + new array)
- GC pressure: Old arrays become garbage after writes
- Temporary arrays: Each write creates a new array
JAVA(8 lines)CodeLoading syntax highlighter...
Question 4: CopyOnWriteArraySet contains() Complexity
JAVA(10 lines)CodeLoading syntax highlighter...
Question 5: Safe Publication
JAVA(12 lines)CodeLoading syntax highlighter...
๐ Summary & Key Takeaways
Copy-On-Write Semantics
- Write operations create new array copy
- Read operations use current array (no copy)
- Iterators see snapshot at creation time
- Never throws ConcurrentModificationException
When to Use
- Few writes, many reads
- Event listener patterns
- Small lists/sets (<100-1000 elements)
- Need safe concurrent iteration
When NOT to Use
- Large collections (memory spikes)
- Frequent writes (O(n) each)
- contains-heavy operations (O(n) for Set)
- Size-sensitive applications
Performance Characteristics
- Read: O(1), no locking
- Write: O(n), creates copy
- Iteration: O(n), snapshot
- Memory: 2x peak during writes
๐ Conclusion
Copy-On-Write collections solve a specific problem elegantly: safe concurrent iteration without locking. They're perfect for event listeners, configuration caches, and other read-heavy scenarios. But they come with trade-offs in memory and write performance that must be understood.
Key takeaways:
- Perfect for listener patterns - few registrations, many notifications
- Snapshot iterators never fail - but don't see concurrent modifications
- Memory doubles during writes - batch updates with addAll()
- O(n) contains for Set - use ConcurrentHashMap.newKeySet() for large sets
- Keep collections small - COW doesn't scale for large data
In the next article, we'll explore BlockingQueues - the foundation of producer-consumer patterns and thread coordination in concurrent applications.
๐ Review Schedule
To solidify your understanding, review this material:
- Tomorrow: Implement an event dispatcher with COW
- In 3 days: Compare COW vs synchronized for your use cases
- In 1 week: Review memory implications and batch update patterns
- In 2 weeks: Practice choosing between COW and alternatives