Java 可见性:构造后最终静态非线程安全集合发生变化

Java visibility: final static non-threadsafe collection changes after construction

我在 luaj 中找到了 following code snippet,我开始怀疑是否有可能在构建 Map 之后对它所做的更改可能对其他线程不可见,因为那里没有同步到位。

我知道,由于 Map 被声明为 final,其构造后的初始化值对其他线程可见,但是之后发生的变化呢。

有些人可能还意识到这个 class 不是线程安全的,以至于在多线程环境中调用强制甚至可能导致 HashMap 中的无限循环,但我的问题不是关于那。

public class CoerceJavaToLua {
    static final Map COERCIONS = new HashMap(); // this map is visible to all threads after construction, since its final

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

    ...
}

你是对的,对 Map 的更改可能对其他线程不可见。每个访问 COERCIONS(读和写)的方法都应该是同一个对象上的 synchronized。或者,如果您永远不需要原子访问序列,则可以使用 synchronized collection.

(顺便说一句,你为什么要使用原始类型?)

两个选项:

// Synchronized (since Java 1.2)
static final Map COERCIONS = Collections.synchronizedMap(new HashMap());

// Concurrent (since Java 5)
static final Map COERCIONS = new ConcurrentHashMap();

它们各有优缺点。

ConcurrentHashMap pro 无锁定。缺点是操作不是 atomic,例如一个线程中的 Iterator 和另一个线程中对 putAll 的调用将允许迭代器看到 一些 添加的值。

这段代码实际上很糟糕,可能会导致很多问题(可能不是无限循环,TreeMap更常见,HashMap更有可能由于覆盖或可能是一些随机异常)。你是对的,不能保证在一个线程中所做的更改会被另一个线程看到。

这里的问题可能看起来不是很大,因为这个 Map 用于缓存目的,因此静默覆盖或可见性滞后不会导致真正的问题(只有两个不同的强制实例将用于相同的 class,在这种情况下可能没问题)。然而,这样的代码仍然有可能破坏您的程序。喜欢的话可以提交补丁给LuaJ团队