javamultithreadingkotlininstance-variablesmultiple-instances

What happens to consumer flow for instance when replaced by another one


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.

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


Solution

  • 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 val or something from a framework designed with concurrency in mind).

    I see two issues with your code:

    You can solve both of this issues by marking classBInstance with @Volatile. This ensures visibility and also establishes a synchronization order as well as a happens-before relation between accesses. When one thread sets classBInstance, 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 @Volatile will probably make your code run slower.

    With this, your code would look as follows:

    @Singleton
    class ClassA {
        @Volatile//CHANGE HERE
        private var classBInstance: ClassB
    
        init{
            classBInstance = ClassB()
        }
    
        fun performOperation(): Result {
            return classBInsatnce.doSomething()
            // doSomething() takes about 4 seconds
         }
    
         fun updateClassB() {
           classBInstance = ClassB()
    
         }
    }
    

    Now to

    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.

    Yes. It doesn't replace the instance after it has been read. Each access to a variable (unless it's a Long/Double and other things using Java's long/double which are tearable) uses exactly one of the variables being set. However, without @Volatile, it may use an uninitialized/incompletely initialized instance of ClassB (i.e. some fields may be null or similar - this includes non-nullable fields).

    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()

    Don't worry, the JVM doesn't do that unless you ask classBInstance from a ClassA instance (not this) inside doSomething.

    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.