So I was writing a unit test to test some multi-threading, and I want to know if this code is guaranteed to work as I would expect.
fun testNumbers() {
var firstNumber: Int? = null
var secondNumber: Int? = null
val startLatch = CountDownLatch(2)
val exec = Executors.newFixedThreadPool(2)
exec.submit({
startLatch.countDown()
startLatch.await()
firstNumber = StuffDoer.makeNumber()
})
exec.submit({
startLatch.countDown()
startLatch.await()
secondNumber = StuffDoer().makeNumber()
})
while (firstNumber == null || secondNumber == null) {
Thread.sleep(1)
}
}
Specifically, is this method guaranteed to complete? firstNumber
and secondNumber
aren't volatile
so does that mean the results set in those values from the exec
threads might never be seen by the thread running the test? You can't apply volatile to local variables, so practically speaking it wouldn't make sense to me that you can't make function-local variables volatile if it might be necessary.
(I added Java as a tag because presumably the basic question is the same in Java.)
When compiled with the Kotlin 1.1 RC compiler, the local variables in your code are stored in ObjectRef
s, which are then used in the lambdas.
You can check what a piece of code is compiled to using the Kotlin bytecode viewer.
ObjectRef
stores the reference in a non-volatile field, so there is indeed no guarantee that the program completes.
Earlier versions of Kotlin used to have a volatile
field in the Ref
classes, but this was an undocumented implementation detail (i.e. not something to rely on) that has eventually been changed in Kotlin 1.1. See this thread for the motivation behind the non-volatile captured variables.
As said in the issue description,
If a user is capturing a variable and handing it to other threads to work with, then it is a requirement of whatever concurrency control mechanism they are using to establish the corresponding happens-before edges between reads and writes to the captured variables. All regular concurrency mechanisms like starting/joining threads, creating futures, etc do so.
To make your example program correctly synchronized, it is enough to call .get()
on the two Future
instances returned from exec.submit { }
, since Future
provides happens-before guarantees:
Actions taken by the asynchronous computation represented by a
Future
happen-before actions subsequent to the retrieval of the result viaFuture.get()
in another thread.
val f1 = exec.submit { /* ... */ }
val f2 = exec.submit { /* ... */ }
f1.get()
f2.get()
// Both assignments made in the submitted tasks are visible now
assert(firstNumber != null)
assert(secondNumber != null)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With