Standard Java collections like ArrayList and HashMap aren't thread-safe for concurrent access. Java provides specialized concurrent collections that offer better performance and safety in multi-threaded environments.
| Standard Collection | Thread-Safe Alternative |
|---|---|
| ArrayList | CopyOnWriteArrayList |
| HashSet | CopyOnWriteArraySet |
| HashMap | ConcurrentHashMap |
CopyOnWriteArrayList
This implementation is ideal for read-heavy scenarios where writes are infrequent. It maintains thread safety by creating a new copy of the underlying array on each modification.
public class ThreadSafeListExample {
public static void main(String[] args) {
List<String> safeList = new CopyOnWriteArrayList<>();
// Multiple threads can safely access this list
for (int i = 0; i < 5; i++) {
new Thread(() -> {
safeList.add(Thread.currentThread().getName());
System.out.println(safeList);
}).start();
}
}
}
The implementation uses a ReentrantLock to ensure atomic writes:
public boolean add(E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
Object[] newArray = Arrays.copyOf(current, current.length + 1);
newArray[current.length] = element;
setArray(newArray);
return true;
} finally {
lock.unlock();
}
}
ConcurrentHashMap
This is the modern replacement for Hashtable, offering better performance through finer-grained locking.
JDK 8 implementation uses synchronized blocks on individual buckets rather than segment locks:
final V putVal(K key, V value, boolean onlyIfAbsent) {
// Null checks omitted for brevity
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break;
}
else {
V oldVal = null;
synchronized (f) {
// Handle existing bucket
}
}
}
addCount(1L, binCount);
return null;
}
The implementation uses several optimization:
- CAS operations for empty buckets
- Synchronized blocks for occupied buckets
- Automatic resizing when needed
- Tree conversion for long chains
For counting elements, it uses a striped counter approach to reduce contention:
private final void addCount(long x, int check) {
CounterCell[] as;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
// Handle contention
}
// Potential resize handling
}