Skip to content
On this page

Concurrent Collections

Previously, we implemented a BlockingQueue using ReentrantLock and Condition:

java
public class TaskQueue {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private Queue<String> queue = new LinkedList<>();

    public void addTask(String s) {
        lock.lock();
        try {
            queue.add(s);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public String getTask() {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                condition.await();
            }
            return queue.remove();
        } finally {
            lock.unlock();
        }
    }
}

The term BlockingQueue means that when a thread calls the getTask() method of this TaskQueue, the method may cause the thread to enter a waiting state until the queue condition is not empty. Once the condition is satisfied and the thread is awakened, the getTask() method will return.

Because BlockingQueue is extremely useful, we don't need to implement it ourselves. Instead, we can directly use the thread-safe collections provided by Java's standard java.util.concurrent package, such as ArrayBlockingQueue.

In addition to BlockingQueue, the java.util.concurrent package also provides concurrent collection classes for List, Map, Set, Deque, and more. Here's a summary:

InterfaceNon-Thread-Safe ImplementationThread-Safe Implementation
ListArrayListCopyOnWriteArrayList
MapHashMapConcurrentHashMap
SetHashSet / TreeSetCopyOnWriteArraySet
QueueArrayDeque / LinkedListArrayBlockingQueue / LinkedBlockingQueue
DequeArrayDeque / LinkedListLinkedBlockingDeque

Using these concurrent collections is identical to using their non-thread-safe counterparts. For example, with ConcurrentHashMap:

java
Map<String, String> map = new ConcurrentHashMap<>();
// Reading and writing in different threads:
map.put("A", "1");
map.put("B", "2");
map.get("A"); // Returns "1"

Because all synchronization and locking logic is handled internally within the collection, external callers only need to use the standard interface. This makes the code identical to using non-thread-safe collections, except when multi-threaded access is required. To switch from a non-thread-safe to a thread-safe collection, simply change:

java
Map<String, String> map = new HashMap<>();

to:

java
Map<String, String> map = new ConcurrentHashMap<>();

The java.util.Collections utility class also provides an older way to create thread-safe collections by wrapping non-thread-safe ones. For example:

java
Map<String, String> unsafeMap = new HashMap<>();
Map<String, String> threadSafeMap = Collections.synchronizedMap(unsafeMap);

However, this approach uses a wrapper class that synchronizes all read and write methods using synchronized blocks. The performance of these thread-safe collections is significantly lower compared to those in the java.util.concurrent package. Therefore, using the concurrent collections provided by java.util.concurrent is recommended over the synchronized wrappers.

Summary

Using the thread-safe concurrent collections provided by the java.util.concurrent package greatly simplifies multithreaded programming:

  • Concurrent collections allow safe simultaneous reading and writing by multiple threads.
  • Prefer using the concurrent collections provided by Java's standard library to avoid writing your own synchronization code.
Concurrent Collections has loaded