Appearance
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:
Interface | Non-Thread-Safe Implementation | Thread-Safe Implementation |
---|---|---|
List | ArrayList | CopyOnWriteArrayList |
Map | HashMap | ConcurrentHashMap |
Set | HashSet / TreeSet | CopyOnWriteArraySet |
Queue | ArrayDeque / LinkedList | ArrayBlockingQueue / LinkedBlockingQueue |
Deque | ArrayDeque / LinkedList | LinkedBlockingDeque |
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.