why it needs to relinquish all held read locks when acquiring a write lock in the first place
This is simply the way how the ReentrantReadWriteLock works. Quoting from the javadoc: “a writer can acquire the read lock, but not vice-versa. Among other applications, reentrancy can be useful when write locks are held during calls or callbacks to methods that perform reads under read locks. If a reader tries to acquire the write lock it will never succeed.”
Now, back to my point. There are indeed multiple use cases for rw lock. In the ReentrantReadWriteLock.java class javadoc there is indeed the CachedData example, for which the Kotlin’s write
fun may be usable (although please notice that the CachedData example only releases its own thread’s lock, and not all locks as it is done in write
).
However, imagine the following case:
class ThreadSafeList {
private val lock = ReentrantReadWriteLock()
private val list = ArrayList<String>()
fun get(index: Int) = lock.read { list[index] }
fun add(str: String) = lock.write { list.add(str) }
}
This is a perfectly valid use case of a rw lock. However, in Kotlin’s impl the write thread will release all read locks and enter the write block, even when there are multiple read threads still ongoing in the read block. An unfortunate race condition may happen:
- list.add() needs to increase its internal array capacity, allocates a new array full of nulls and gradually starts to copy the old one over the newly allocated one
- because of how the processor cache works, read thread may see those operations in random (even reverted order). So it is possible to read null from the newly created array.
So, the get() fun will randomly read nulls under a heavy load, which makes this bug extremely nasty - it is hard to simulate and only occurs randomly. Be very careful when working with threads 