The curious case of transient final locks and CopyOnWriteArrayList

Every now and then I discover a new class in the Java standard library that I’ve not needed to use before. Today, that class was CopyOnWriteArrayList (or rather the CopyOnWriteArraySet wrapper). This is a good choice when implementing a set of subscribers in an observer pattern, as typically the set of subscribers doesn’t change very often but we iterate through the set regularly to broadcast events. The COW implementations are optimised for exactly this case, as the iterator only hits a volatile read rather than needing a lock.

Being curious, I decided to have a look at the implementation of this class to verify that it behaved as I expected from the docs. Sure enough, everything is as I would expect. However, something did stick out immediately as a bit odd. Locking on mutation is performed via a per-instance ReentrantLock rather than a synchronized block, and that field is declared as:

transient final ReentrantLock lock = new ReentrantLock();

I’ve not seen the ‘transient final’ combination before, and it stuck out like a sore thumb. I can understand why the lock would be ‘final’: that is just good sense with any kind of lock. I can sort of understand why the lock would be transient, but then ReentrantLock is Serializable so it should just work, shouldn’t it? Given this is by Doug Lea, I assume there is a good reason for it, but I can’t find anything explaining the rationale. Furthermore, the way that the lock is re-initialised on clone/deserialisation is even more bizarre:

// Support for resetting lock while deserializing
private static final Unsafe = Unsafe.getUnsafe();
private static final long lockOffset;
static {
    try {
        lockOffset = unsafe.objectFieldOffset
            (CopyOnWriteArrayList.class.getDeclaredField("lock"));
        } catch (Exception ex) { throw new Error(ex); }
    
}
private void resetLock() {
    unsafe.publishVolatile(this, lockOffset, new ReentrantLock());
}

Odd, very odd. If we cannot rely on the lock deserialising correctly, why not use the reflection approach as detailed in this StackOverflow answer? It all seems very ugly — another reason to just avoid Java serialization whenever possible.

Author: Neil Madden

Founder of Illuminated Security, providing application security and cryptography training courses. Previously Security Architect at ForgeRock. Experienced software engineer with a PhD in computer science. Interested in application security, applied cryptography, logic programming and intelligent agents.

%d bloggers like this: