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 throw ConcurrentModificationException here when another thread call addTen() the same time

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