在《 java 并发实战》中 3.4.2 使用 volatile 发布不可变对象 章节中有一段代码理解不了,希望大家能不能给一个更通俗的解释。
@Immutable
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) {
if (lastNumber == null || !lastNumber.equals(i))
return null;
else
return Arrays.copyOf(lastFactors, lastFactors.length);
}
}
public class VolatileCachedFactorizer implements Servlet {
private volatile OneValueCache cache =
new OneValueCache(null, null);
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = cache.getFactors(i);
if (factors == null) {
factors = factor(i);
cache = new OneValueCache(i, factors); // 这一行看起来不像是线程安全。
}
encodeIntoResponse(resp, factors);
}
}
这里原文解释说 VolatileCacheFactorizer 是线程安全的。但是我看起来 cache = new OneValueCache(i, factors)这行是线程不安全的。
虽然 OneValueCache 是不可变对象,但是 VolatileCachedFactorizer 里的 cache 是有可能被多个线程同时写入的吧?
希望大佬能帮忙解答一下,谢谢了。
1
Leo666666 2023-08-02 18:14:58 +08:00
这里的 cache 是被 volatile 修饰的,当一个变量被 volatile 修饰时,它具有以下特性:
1. 可见性:对一个 volatile 变量的写操作能立即被其他线程所看到,而其他线程的读操作也能得到最新值。 2. 有序性:加入 volatile 修饰的变量在编译器和 CPU 层面会进行指令重排的优化,保证 volatile 变量之前的操作不会被编译器和 CPU 重排到 volatile 变量之后。 |
2
QiWa 2023-08-02 18:19:48 +08:00
volatile 可以看成一个轻量 synchronized
|
3
zhzy0077 2023-08-02 18:45:46 +08:00 via Android
变量赋值是原子的 多个线程同时写入最后一个为准
|
4
mango88 2023-08-02 19:53:20 +08:00 1
cache = new OneValueCache(i, factors) 写了之后,由于有 volatile 存在 另外一个线程执行 cache.getFactors(i) 立马可见
你提到的 `cache 是有可能被多个线程多个线程同时写入的` 是有可能的, 但是从他的业务来看,虽然是不同的 cache 对象,但是不会引起业务错误,这应该是他想表达“线程安全”的意思 |
5
lingalonely 2023-08-02 20:11:20 +08:00
|
6
halozzz 2023-08-02 20:12:44 +08:00 via iPhone
注意 OneValueCache 里的操作都是 copy 的,不是直接赋值和返回值,这里的线程安全是指这个类两次初始化数据不会互相干扰,而不是只会初始化一次。
|
7
halozzz 2023-08-02 20:20:39 +08:00 via iPhone
@lingalonely
Nothing guarantees that two threads won't compute the factors from the same number. The only guarantee that this code offers is that, if the cache currently contains the factors for the requested number, these cached factors will be returned (and not factors for another number, or inconsistent data cause by a data race) race condition 依旧存在,不过能保证的是返回的值不会因为 race 存在而有不一样的结果 |
8
Nerv 2023-08-02 20:22:18 +08:00
1. 引用写入具备原子性
2. volatile 写入使引用具备可见性,OneValueCache 的属性是 final 的,final 的特殊规则说明其初始化之后对所有线程可见,因此属性访问也满足可见性,其总体满足可见性 3. 即使被多个线程同时写入,导致的后果也就是多创建了几次对象(多余的对象会被 gc ),cache 存储的是最后一次被写入的值,不对后续 cache 的使用造成影响,满足一致性。 |
10
labilixin OP 感谢各位的解答,我最后的理解是,其实这个不是线程安全的。只不过他返回的值跟 cache 没关系。所以最后的结果是线程安全的。像 8 楼 @Nerv 说的一样,我也比较倾向于这种解释。
感觉这个这本书里这个例子举的有点迷惑性。如果他是返回值跟 cache 有关系的话其实感觉会有可能返回意料之外的结果的。 |
11
ZiChun 2023-08-04 16:49:15 +08:00
ChatGPT 如是说:
在这段代码中,虽然看起来 cache = new OneValueCache(i, factors) 这一行在没有同步机制的情况下,似乎是线程不安全的,但实际上由于 volatile 关键字和 OneValueCache 对象的不可变性,使得 VolatileCachedFactorizer 在多线程环境中仍然是线程安全的。即使多个线程试图同时创建新的 OneValueCache 对象并赋值给 cache ,但因为 volatile 关键字的内存可见性特性,所有线程都会读取到最新的 cache 值。另外,由于 OneValueCache 对象的不变性,即使 cache 被新的 OneValueCache 对象替换,其他线程获取的旧 OneValueCache 对象仍然是一致和有效的,因此并不会影响程序的正确性。 |