Chapter 05 - Building Block
Synchronised collections
The classed created from Collections.synchronised
are mostly threadsafe. For example:
Collections.synchronizedList()
Will return a list that will be thread safe even in combound operation no matter how many thread using the same time. This is because each of the class method is used with Synchronisation and therefore locking on the whole object.
However, this still potentially cause some problem. Consider the following example
public Integer getLast(List<Integer> syncList) {
int lastIndex = syncList.size() - 1;
return syncList.get(lastIndex);
}
public Integer removeLast(List<Integer> syncList) {
int lastIndex = syncList.size() - 1;
return syncList.remove(lastIndex);
}
This might cause a ArrayIndexOutOfRange if the removeLast
get called before the getLast
Thread1---LastIndex=10------------------->getLast() <Throw index out of range>
Thread2---Lastindex=10--->deleteLast()
To solve this, we can synchronise the outer action as well
public Integer getLast(List<Integer> syncList) {
synchronized(syncList) {
int lastIndex = syncList.size() - 1;
return syncList.get(lastIndex);
}
}
public Integer removeLast(List<Integer> syncList) {
synchronized(syncList) {
int lastIndex = syncList.size() - 1;
return syncList.remove(lastIndex);
}
}
Note that if some one iterating through this list like this:
@NotThreadSafe
for (int i = 0; i < syncList.size(); i++) {
syncList.get(i);
}
This is not our problem, the 2 above methods are thread safe. User can sacrifice some performance and lock on syncList
while going through.
@NotThreadSafe
synchronized(syncList) {
for (int i = 0; i < syncList.size(); i++) {
syncList.get(i);
}
}
Alternative to locking the list while going through is to cline this list instead and looping through the copy. Of course this will come with a performance cost
Iterator and concurrent modification
The iterator from Collections.synchronizedList
are not thread safe and is a fail-fast
List<T> list = Collections.synchronized(new ArrayList<Integer>());
for (Integer i : list) {
doSomething(i); // May throw ConcurrentModificationException
}
Sometimes, the reasons for the code fail could be hidden, for example, consider the code below:
class HiddenIterator {
@GuardedBy("this") private final Set<Integer> integerSet = new HashSet<Integer>();
public synchronized void add(Integer integer) { integerSet.add(integer); }
public synchronized void remoev(Integer integer) { integerSet.remoev(integer); }
public void addTen() {
var r = new Random();
for (int i = 0; i < 10; i++) {
add(r.nextInt())
}
System.out.println("DEBUG: Added 10 elements " + integerSet); // May throw ConcurrentModificationException
}
}
The reason is, when we do string concatenation, the compiler will turn into calling
StringBuilder.append(object)
- Loop through each object and call
toString()
which may throwConcurrentModificationException
here when another thread calladdTen()
the same time
- Loop through each object and call
Of course the fix for it would be to client lock the integerSet
when debugging as well, however since this is debugging, sometimes we don't pay attention and put the code there.
Similarly, if one calls method such as containsAll
, removeAll
… which will in turns use iterations, which will cause ConcurrentModificationException