在java中,当一个线程在调用一个字段上的方法的过程中被挂起,而另一个线程改变了该字段时,会发生什么?

vfhzx4xs  于 2023-03-21  发布在  Java
关注(0)|答案(3)|浏览(139)

我在阅读Java Concurrency In Practice时,看到了这样一个程序:

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) +
                '}';
    }
}

因为类OneValueCache是不可变的,所以它在TestImmutable中的使用是线程安全的。
但是我很好奇,如果一个线程在执行**cache.getFactors(...)时被挂起,然后另一个线程更新了cache引用,那么第一个线程继续执行代码。在这种情况下,第一个线程正在处理哪个对象,第二个线程更新之前还是之后?
上面的代码是为了创建这样的场景,但是我不能控制线程何时何地被挂起,所以我不认为它工作正常。

htrmnn0y

htrmnn0y1#

。。。调用字段上的方法。。
你不调用字段上的方法。你调用 * 对象上的方法。* 当你写cache.getFactors(i,sec)时,这是在cache引用的 * 对象 * 上调用getFactors方法。让我们将该对象称为O1,让我们将getFactors方法的调用称为Call1
如果其他线程随后设置了cache=new OneValueCache(i,factors),则cache现在引用不同的对象O2。但是,这不会改变Call1的目标对象。如果该函数调用仍在进行中,则它仍在O1上工作。当Call1访问lastFactorslastNumber时,它仍然在访问O1对象的字段。
即使另一个线程随后调用了cache.getFactors(i,sec),这仍然是正确的。这就是在O2上工作的Call2。但是,Call1对此一无所知。它将继续在O1上工作,直到它返回。

vuv7lop3

vuv7lop32#

由于OneValueCache本身是线程安全的,因此当另一个线程可能正在更改cache时,调用cache.getFactors()实际上只有两种可能的结果:

  • 要么线程在另一个线程更新它之前从内存中读取cache,并获得旧值 * 或 *
  • 线程在另一个线程更新cache并获得新值之后“从存储器中读取cache

但是由于cache从未被分配null,并且OneValueCache的所有示例都正确地运行(一个示例可能没有缓存“所需”的值,但如果没有,它将正确地返回null,这在service()中得到了正确的处理),所以没有什么会中断:代码不依赖于cache多次具有相同的值(这是竞争条件的常见来源),因为它只读取一次。

rm5edbpk

rm5edbpk3#

原因是,如果一个线程更新cache,而另一个线程正在阅读它,第二个线程将看到对OneValueCache的旧示例或新示例的引用(这是由volatile变量保证的)。
但是可能有一个问题,假设服务调用getFactors两次。

public void service(int sec) {
        BigInteger[] one = cache.getFactors(i, sec);
        [...]
        BigInteger[] two = cache.getFactors(i, sec);
    }

在这种情况下,底层缓存的值可能已经改变了。如果这是一件坏事,代码可以首先将cache分配给一个局部变量并引用它。

相关问题