Java 可见性:final static 非线程安全集合在构造后发生更改。

我在 luaj 中发现了以下的代码片段,并开始怀疑,在构建Map之后对其所做的更改是否可能对其他线程不可见,因为没有进行同步。

我知道,由于Map被声明为final,因此其构建后的初始化值是对其他线程可见的,但是发生在其中之后的更改呢?

有些人可能也注意到,这个类明显不是线程安全的,即在多线程环境中调用强制转换(coerce)可能会导致HashMap中的无限循环,但我的问题不是关于这个的。

public class CoerceJavaToLua {
    static final Map COERCIONS = new HashMap(); // 这个 Map 构建后所有线程都可见,因为它是 final 的

    public static LuaValue coerce(Object paramObject) {
        ...;
        if (localCoercion == null) {
            localCoercion = ...;
            COERCIONS.put(localClass, localCoercion); // 可见吗?
        }
        return ...;
    }

    ...
}
点赞
用户1953590
用户1953590

你是正确的,对 Map 的更改可能不会对其他线程可见。访问 COERCIONS 的所有方法(读取和写入)都应在相同的对象上进行 synchronized。或者,如果你永远不需要对访问序列进行原子化,则可以使用 同步集合

(另外,你为什么要使用原始类型?)

2015-08-25 03:39:32
用户5221149
用户5221149

两种选择:

// 同步 (自 Java 1.2 起)
static final Map COERCIONS = Collections.synchronizedMap(new HashMap());

// 并发 (自 Java 5 起)
static final Map COERCIONS = new ConcurrentHashMap();

它们都有优缺点。

ConcurrentHashMap 的优点是_无锁_。缺点是操作不是_原子的_,例如,一个线程中的 迭代器 和另一个线程中的 putAll 调用将允许迭代器查看_一些_添加的值。

2015-08-25 03:47:41
用户4856258
用户4856258
这段代码实际上很糟糕,可能会导致许多问题(可能不是无限循环,这更常见于 `TreeMap`,而 `HashMap` 更可能由于覆盖而导致静默数据丢失,或者可能会出现一些随机异常)。而且,你是正确的,无法保证一个线程所做的更改将被另一个线程看到。

在这里,问题看起来不是很大,因为这个 `Map` 用于缓存目的,因此静默覆盖或可见性滞后不会导致实际问题(同一个类会使用两个不同的 coercion 实例,这在这种情况下可能是可以的)。然而,这样的代码仍然可能会破坏您的程序。如果您愿意,可以向 LuaJ 团队提交补丁。
2015-08-25 03:50:47