我在阅读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
引用,那么第一个线程继续执行代码。在这种情况下,第一个线程正在处理哪个对象,第二个线程更新之前还是之后?
上面的代码是为了创建这样的场景,但是我不能控制线程何时何地被挂起,所以我不认为它工作正常。
3条答案
按热度按时间htrmnn0y1#
。。。调用字段上的方法。。
你不调用字段上的方法。你调用 * 对象上的方法。* 当你写
cache.getFactors(i,sec)
时,这是在cache
引用的 * 对象 * 上调用getFactors
方法。让我们将该对象称为O1
,让我们将getFactors
方法的调用称为Call1
。如果其他线程随后设置了
cache=new OneValueCache(i,factors)
,则cache
现在引用不同的对象O2
。但是,这不会改变Call1
的目标对象。如果该函数调用仍在进行中,则它仍在O1
上工作。当Call1
访问lastFactors
或lastNumber
时,它仍然在访问O1
对象的字段。即使另一个线程随后调用了
cache.getFactors(i,sec)
,这仍然是正确的。这就是在O2
上工作的Call2
。但是,Call1
对此一无所知。它将继续在O1
上工作,直到它返回。vuv7lop32#
由于
OneValueCache
本身是线程安全的,因此当另一个线程可能正在更改cache
时,调用cache.getFactors()
实际上只有两种可能的结果:cache
,并获得旧值 * 或 *cache
并获得新值之后“从存储器中读取cache
。但是由于
cache
从未被分配null
,并且OneValueCache
的所有示例都正确地运行(一个示例可能没有缓存“所需”的值,但如果没有,它将正确地返回null
,这在service()
中得到了正确的处理),所以没有什么会中断:代码不依赖于cache
多次具有相同的值(这是竞争条件的常见来源),因为它只读取一次。rm5edbpk3#
原因是,如果一个线程更新
cache
,而另一个线程正在阅读它,第二个线程将看到对OneValueCache
的旧示例或新示例的引用(这是由volatile变量保证的)。但是可能有一个问题,假设服务调用
getFactors
两次。在这种情况下,底层缓存的值可能已经改变了。如果这是一件坏事,代码可以首先将
cache
分配给一个局部变量并引用它。