I have a class which has an internal instance of another class.
@Singleton
class ClassA {
private var classBInstance: ClassB
init{
classBInstance = ClassB()
}
fun performOperation(): Result {
return classBInsatnce.doSomething()
// doSomething() takes about 4 seconds
}
fun updateClassB() {
classBInstance = ClassB()
}
}
ClassA is a singleton is used by multiple threads which call performOperation() on it. Now, if a consumer calls performOperation and thus internally classBInstance.doSomething() is called, and while this is being processed (for 4 seconds), if someone else calls updateClassB(), due to which the variable classBInstance now holds a new instance if ClassB. So what will happen to the earlier consumer, whose operation (doSomething() is still in progress in the previous instance of ClassB.
- Will doSomething() still perform its full operation, using the earlier instance of ClassB? for this consumer. And for any further requests, doSomething of new instance of ClassB is used.
- Or would there be some issue in the doSomething() of the previous ClassB instance, as soon as the variable is replace.
I ask, coz the idea is that I would like to makes sure that when the instance held by classBInstance is updated to new instances, any existing calls being processed by performOperation() are done with older instances of ClassB and only after ClassB new instance is set into classBInstance variable, then only new instance classB is used for any further calls to performOperation()
Could someone please help clarify this? Thanks
I have tried to read about class instances and how they operate but had this confusion about this edge case
For the most part, the Java Memory Model should apply here.
First of all, I am assuming that your singleton is created in a thread-safe way (e.g. it's a
valor something from a framework designed with concurrency in mind).I see two issues with your code:
ClassB. When you doclassBInstance = ClassB(), it might happen that the instructions are reordered and the assignment happens beforeClassBis fully initialized.classBInstance, it may not be visible to other threads due to CPU caching. You need to ensure thatclassBInstanceis updated.You can solve both of this issues by marking
classBInstancewith @Volatile. This ensures visibility and also establishes a synchronization order as well as a happens-before relation between accesses. When one thread setsclassBInstance, initialization will happen-before assigning the variable which will happen-before further reads of the variable and the JVM will ensure that subsequent reads (by other threads) read the updated value.However, note that
@Volatilewill probably make your code run slower.With this, your code would look as follows:
Now to
Yes. It doesn't replace the instance after it has been read. Each access to a variable (unless it's a
Long/Doubleand other things using Java'slong/doublewhich are tearable) uses exactly one of the variables being set. However, without@Volatile, it may use an uninitialized/incompletely initialized instance ofClassB(i.e. some fields may benullor similar - this includes non-nullable fields).Don't worry, the JVM doesn't do that unless you ask
classBInstancefrom aClassAinstance (notthis) insidedoSomething.Also note that the kinds of concurrency issues may or may not happen. This can vary across subsequent calls, JVM implementations, JVM instances, operating systems, and CPU architectures. The JVM ensures that the guarantees from the memory model hold but as long as it behaves according to these requirements, there can be multiple possible "valid" (according to the JVM) operations. Bugs resulting from incorrect concurrent Java/Kotlin/JVM-language code may manifest themselves whenever the JVM feels like it.
Disclaimer
I am a Java developer and not a Kotlin developer. It is easily possible that I have overlooked something specific to Kotlin.
If anyone notices anything wrong anywhere, feel is free to tell me in the comments.