Design a shared data structure that allows multiple threads to insert numbers and check for two-sum pairs without race conditions.
Requirements:
Single Shared Store:
- A globally accessible collection (e.g.,
ConcurrentHashMap
,HashSet
with locks) to store numbers.
- A globally accessible collection (e.g.,
Two Operations:
add(num)
: Insert a number into the store.checkPair(num, target)
: Check if(target - num)
exists in the store.
Thread Safety:
- Handle concurrent
add()
andcheckPair()
calls safely.
- Handle concurrent
Approach:
Use fine-grained locking (e.g.,
synchronized
blocks,ReentrantLock
) or aConcurrentHashMap
.Ensure atomicity for compound operations (e.g., check-then-add).
Solution
shared data structure for concurrent two-sum operations using Java's ConcurrentHashMap
for thread safety:
import java.util.concurrent.ConcurrentHashMap;
public class TwoSumStore {
private final ConcurrentHashMap<Integer, Integer> numberCounts;
public TwoSumStore() {
this.numberCounts = new ConcurrentHashMap<>();
}
/**
* Adds a number to the store in a thread-safe manner
* @param num the number to add
*/
public void add(int num) {
numberCounts.compute(num, (key, value) -> value == null ? 1 : value + 1);
}
/**
* Checks if there exists a number in the store that can pair with the given num to sum to target
* @param num the number to check for a complement
* @param target the desired sum
* @return true if a complement exists, false otherwise
*/
public boolean checkPair(int num, int target) {
int complement = target - num;
// Special case: num == complement, we need at least two occurrences
if (num == complement) {
Integer count = numberCounts.get(complement);
return count != null && count >= 2;
}
return numberCounts.containsKey(complement);
}
}
Alternative Implementation with ReadWriteLock
For cases where you need more control over locking, here's a version using ReadWriteLock
:
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TwoSumStoreWithLock {
private final Set<Integer> numbers;
private final ReadWriteLock lock;
public TwoSumStoreWithLock() {
this.numbers = new HashSet<>();
this.lock = new ReentrantReadWriteLock();
}
public void add(int num) {
lock.writeLock().lock();
try {
numbers.add(num);
} finally {
lock.writeLock().unlock();
}
}
public boolean checkPair(int num, int target) {
int complement = target - num;
lock.readLock().lock();
try {
// Special case: num == complement, need at least two occurrences
if (num == complement) {
return numbers.contains(complement) &&
numbers.stream().filter(n -> n == complement).count() >= 2;
}
return numbers.contains(complement);
} finally {
lock.readLock().unlock();
}
}
}
Key Features
Thread Safety:
First version uses
ConcurrentHashMap
which handles concurrency internallySecond version uses
ReadWriteLock
for explicit control
Correctness:
Handles the special case where a number is its own complement (e.g., 5 + 5 = 10)
Operations are atomic and won't cause race conditions
Performance:
ConcurrentHashMap
allows concurrent reads and limited concurrent writesReadWriteLock
allows multiple concurrent readers but exclusive writer access
Scalability:
Both implementations minimize lock contention
ConcurrentHashMap
version is generally more scalable for high contention scenarios
Follow-up: (coming in Next Blog)
How to avoid duplicate pair notifications?
What if producers overwhelm consumers?