Why is this working? (generating values in main thread and consuming them in some other background thread in Android)

Viewed 69

As far as I know, threads copy variables in their local cache. What I'm doing is, I'm getting values in the main thread and then I'm consuming them in some other background (renderer) thread. Like this-

class MySurfaceView ... {
    private var someValue = 0

    // Called from the main thread
    fun updateValue() {
        someValue++
    }
    
    fun render() {
        Thread {
            // Here someValue variable is consumed
            // and it's always up to date.
            ....
        }.start()
    }
}

So, inside the runnable passed to the thread started inside render(), 'someValue' is always up to date and I didn't even mark it volatile. If threads copy variables to their local cache then why is it happening? Is it because an implicit reference of the outer class is being kept and the value is coming from there (if this is true, then in cases like this, where I have one generator and multiple consumer threads, would it always be safe to not mark the field which is updated only by the generator thread as volatile? As read/write operations on volatile is costly)? The same thing also happens when I start a coroutine and try access 'someValue' inside it.

1 Answers

threads copy variables in their local cache.

This is not true, which explains your findings. Basically, you confused the liberties an implementation may exercise with guarantees the specification provides. The Java Memory Model (which Kotlin follows as well) simply gives the implementation the freedom to do whatever it likes with state that doesn't get shared through synchronizing actions. It may store it on the stack or in a register, and it may also work with the original on the heap.

One especially relevant detail is that println() is a synchronized method, so if in your actual code you had printlns in both threads to see what's going on, you introduced synchronization that made the results come out right.

On the other hand, it's quite easy to see a program that takes this freedom, for example

Thread 1:

var runningFlag = true

while (runningFlag) {}

Thread 2:

sleep(1000)
runningFlag = false

Thread 1 is quite likely to go on executing forever, or even to be compiled similar to currentThread().join() -- doing no actual work, but never completing.

Related