Search code examples
javaconcurrency

What happens in java when a thread gets suspended in the middle of invoking a method on a field while another thread changes the field?


I was reading Java Concurrency In Practice, and saw a program like this:

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class TestImmutable {

    private volatile OneValueCache cache =
            new OneValueCache(new BigInteger("1"), new BigInteger[]{new BigInteger("1")});

    public void service(int sec) {
        Random random = new Random();
        BigInteger i = new BigInteger(String.valueOf(random.nextInt()));
        BigInteger[] factors = cache.getFactors(i, sec);
        if (factors == null) {
            factors = new BigInteger[]{i};
            cache = new OneValueCache(i, factors);
            System.out.println("After update: " + cache);
        }
    }

    public static void main(String[] args) {
        TestImmutable testImmutable = new TestImmutable();
        new Thread(() -> testImmutable.service(0)).start();
        new Thread(() -> testImmutable.service(15)).start();
    }

}


class OneValueCache {
    private final BigInteger lastNumber;
    private final BigInteger[] lastFactors;

    public OneValueCache(BigInteger i,
                         BigInteger[] factors) {
        lastNumber = i;
        lastFactors = Arrays.copyOf(factors, factors.length);
    }

    public BigInteger[] getFactors(BigInteger i, int sec) {
        try {
            TimeUnit.SECONDS.sleep(sec);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Inside cache: " + this);
        if (lastNumber == null || !lastNumber.equals(i))
            return null;
        else
            return Arrays.copyOf(lastFactors, lastFactors.length);
    }

    @Override
    public String toString() {
        return "OneValueCache{" +
                "lastNumber=" + lastNumber +
                ", lastFactors=" + Arrays.toString(lastFactors) +
                '}';
    }
}

It says in the book because class OneValueCache is immutable, so its usage in TestImmutable is thread safe.

However I'm curious about what happens if one thread gets suspended when in the middle of executing cache.getFactors(...) and then another thread updates the cache reference, then the first thread continues to execute the code. In this case which object is the first thread is working with, object before or after the second thread updated?

The code above was intended to create a scenario like this, but i can't control when and where the thread gets suspended, so I don't think its working right.


Solution

  • ...invoking a method on a field...

    You don't invoke methods on fields. You invoke methods on objects. When you write cache.getFactors(i,sec), that's invoking the getFactors method on the object to which cache refers. Let's call that object, O1, and let's call that invocation of the getFactors method, Call1.

    If some other thread subsequently sets cache=new OneValueCache(i,factors), then cache now refers to a different object, O2. But, that doesn't change which object was the target of Call1. If that function invocation still is in progress, then it still is working on O1. When Call1 accesses lastFactors or lastNumber, it still is accessing the fields of the O1 object.

    It's still true even when the other thread subsequently invokes cache.getFactors(i,sec). That's Call2 which is working on O2. But, Call1 doesn't know anything about that. It will continue to work on O1 until it returns.