Professor Abdul-Quader
Concurrent Data Structures
A Vector
is like an ArrayList
, with all of its methods synchronized. Is the following code thread-safe?
See ListRaceCondition. What happens when we run the code? Play around with the type of “Executor” we use (single vs multithreaded).
Question: why do we see a NullPointerException
? What is the race condition here?
add
calls the following:
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
synchronizedList
, synchronizedMap
, synchronizedSet
, etc.Moral: may need to synchronize yourself.
// Thread 1:
for (int i = 0; i < 10000; i++) {
list.add(i);
}
// Thread 2:
for (int num : list) {
System.out.println(num);
}
What could go wrong?
ConcurrentModificationException
if they detect changes in another thread while iteratingcontainsAll
, toString
, and other methods may trigger an iteration through the entire collection.Intead of locking: you can clone the collection first.
ArrayList<Integer> list2;
synchronized(list) { // synchronize while cloning
list2 = new ArrayList<>(list);
}
for (int num : list2) {
System.out.println(num);
}
CopyOnWriteArrayList<Integer> l; // initialized somewhere
// Thread 1:
for (int i = 0; i < 1000; i++) {
l.add(i);
}
// Thread 2:
for (int num : l) {
System.out.println(num);
}
What gets printed?
// Thread 1 is the same as before
// Thread 2:
Iterator<Integer> it = l.iterator();
while (it.hasNext()) {
int num = it.next();
it.remove();
}
What happens?
CopyOnWriteArrayList<Integer> l;
// l is initialized somewhere and shared
if (!l.contains(0)) {
l.add(0);
}
Compound operations?
Recall: a HashMap uses “separate chaining” (not probing). What happens as we add more things to a separate chaining HashMap?
“The semantics of methods that operate on the entire Map, such as size and isEmpty, have been slightly weakened to reflect the concurrent nature of the collection. Since the result of size could be out of date by the time it is computed, it is really only an estimate, so size is allowed to return an approximation instead of an exact count. While at first this may seem disturbing, in reality methods like size and isEmpty are far less useful in concurrent environments because these quantities are moving targets. So the requirements for these operations were weakened to enable performance optimizations for the most important operations . . .”