Skip to main content

Synchronization and Concurrent Collections in Java

Introduction​

In a multithreaded environment, multiple threads may try to access and modify shared resources concurrently, leading to inconsistent data and unpredictable behavior. Synchronization is a mechanism that ensures that only one thread can access a resource at a time, providing thread safety. Concurrent collections in Java provide a way to handle collections in a multithreaded environment without requiring explicit synchronization.

1. Synchronization​

Synchronized Methods​

Synchronized methods ensure that only one thread can execute a method at a time on the same object. This is achieved by using the synchronized keyword.

public class Counter {
private int count = 0;

public synchronized void increment() {
count++;
}

public synchronized int getCount() {
return count;
}
}

Synchronized Blocks​

Synchronized blocks provide more fine-grained control over synchronization. You can synchronize only a specific block of code rather than the entire method.

public class Counter {
private int count = 0;

public void increment() {
synchronized (this) {
count++;
}
}

public int getCount() {
synchronized (this) {
return count;
}
}
}

Reentrant Lock​

The ReentrantLock class provides an alternative to using synchronized methods and blocks. It offers more flexibility, such as the ability to try acquiring the lock without blocking.

import java.util.concurrent.locks.ReentrantLock;

public class Counter {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();

public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}

public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}

2. Concurrent Collections​

Java provides several concurrent collection classes that are designed for use in multithreaded environments. These classes handle synchronization internally, making them safer and easier to use than manually synchronized collections.

ConcurrentHashMap​

ConcurrentHashMap is a thread-safe implementation of HashMap. It allows concurrent read and write operations without locking the entire map.

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

map.put("A", 1);
map.put("B", 2);

System.out.println(map.get("A"));
}
}

CopyOnWriteArrayList​

CopyOnWriteArrayList is a thread-safe variant of ArrayList where all mutative operations (add, set, etc.) are implemented by making a fresh copy of the underlying array.

import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

list.add("A");
list.add("B");

for (String item : list) {
System.out.println(item);
}
}
}

BlockingQueue​

BlockingQueue is a thread-safe queue that supports operations that wait for the queue to become non-empty when retrieving an element and wait for space to become available in the queue when storing an element.

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueExample {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

queue.put("A");
System.out.println(queue.take());
}
}

Conclusion​

Synchronization and concurrent collections are essential tools for developing thread-safe Java applications. Synchronization ensures that only one thread can access a resource at a time, preventing data inconsistency and race conditions. Concurrent collections, on the other hand, provide built-in thread safety for common data structures, making it easier to develop concurrent applications without the need for explicit synchronization.

Understanding how to use these tools effectively will help you build robust and efficient multithreaded applications in Java.