Don't Assume Atomicity in Single Call
Don't Assume Atomicity in Single Call
Key Idea
Single operations on shared data structures are not inherently atomic. Without proper synchronization, issues like race conditions, data corruption, and unpredictable behavior can occur.
Example: Kotlin's List<>
Kotlin's List<>
and MutableList<>
are not thread-safe. Even simple calls like list.get(index)
or list.add(element)
can lead to issues in multithreaded environments.
Unsafe Example:
val sharedList = mutableListOf<Int>()
fun unsafeRead(index: Int): Int {
return sharedList[index] // Not thread-safe
}
fun unsafeWrite(value: Int) {
sharedList.add(value) // Not thread-safe
}
Best Practices
- Use Thread-Safe Variants: Replace
List<>
withCopyOnWriteArrayList
or similar. - Synchronize Access: Use
ReentrantLock
orsynchronized
to protect shared data. - Prefer Immutability: Use immutable collections to eliminate synchronization needs.
Takeaway
Do not assume atomicity in single calls. Without thread-safe mechanisms, such assumptions lead to fragile and error-prone systems.
Example in Kotlin
GT-Sandbox-Snapshot: Havoc happening when adding to a list in kotlin from multiple threads.
Code
package com.glassthought.sandbox
import kotlin.concurrent.thread
fun main() {
// Shared mutable list
val sharedList = mutableListOf<Int>()
// Number of threads and iterations per thread
val threadCount = 10
val iterations = 10000
// Threads performing concurrent writes
val threads = List(threadCount) { threadIndex ->
thread {
repeat(iterations) {
sharedList.add(threadIndex * iterations + it) // Unsafe write
}
}
}
// Wait for all threads to finish
threads.forEach { it.join() }
// Analyze the result
println("Expected size: ${threadCount * iterations}")
println("Actual size: ${sharedList.size}")
println("Null elements in the list count: ${sharedList.size - sharedList.filterNotNull().size}")
}
Command to reproduce:
gt.sandbox.checkout.commit ae6f6f8d4f229f078e71 \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "./gradlew run --quiet"
Recorded output of command:
Expected size: 100000
Actual size: 27429
Null elements in the list count: 18