这个类线程安全吗?

时间:2011-04-12 12:18:00

标签: java concurrency thread-safety immutability

您好
类下面是线程安全吗?

class ImmutablePossiblyThreadsafeClass<K, V> {

    private final Map<K, V> map;

    public ImmutablePossiblyThreadsafeClass(final Map<K, V> map) {
        this.map = new HashMap<K, V>();

        for (Entry<K, V> entry : map.entrySet()) {
            this.map.put(entry.getKey(), entry.getValue());
        }
    }

    public V get(K key) {
        return this.map.get(key);
    }
}

8 个答案:

答案 0 :(得分:5)

如果这是整个类的定义,即没有其他的setter / modifier方法,那么map本身的用法是线程安全的:

  • 通过声明final
  • 来保护其初始化和可见性
  • 包含类只有一个getter,没有修饰符方法,
  • 地图的内容是从构造函数参数映射中复制的,因此不能通过外部引用来修改它。

(请注意,这只是指地图的结构 - 地图中的各个元素可能仍然不安全。)

更新

关于在构造过程中同时修改构造函数参数没有保护类的声明,我倾向于同意已经指出

的其他人(包括@Bozho)。
  1. 没有已知的方法来防范这种情况(甚至没有使用@ ConcurrentHashMap作为@Grundlefleck建议 - 我检查了源代码,其构造函数只调用了putAll(),它没有防范这个),
  2. 确保构造函数参数未被同时修改是调用者的责任而不是被调用者。事实上,任何人都可以定义处理同时修改的构造函数参数的“正确”行为吗?创建的对象是否应包含参数映射中的原始元素或最新元素?...如果开始考虑这个问题,恕我直言,不难发现唯一一致的解决方案是禁止并发修改构造函数参数,这只能由来电者确保。

答案 1 :(得分:3)

当您复制构造函数参数的内容时,可能会出现问题。但是你无法做任何事情来防止这种情况发生。另一个潜在的问题是可变键。

此外,您可以使用以下命令替换构造函数:

public ImmutablePossiblyThreadsafeClass(final Map<K, V> map) {
    this.map = new HashMap<K, V>(map);
}

答案 2 :(得分:1)

是的,它是线程安全的。它是不可变的,因此任何线程都不能进行任何会干扰另一个线程结果的更改。

注意:如果在构造过程中修改了地图,您将无法获得您认为已通过的内容,但之后访问地图时,它将是线程安全的。

答案 3 :(得分:1)

假设在构造之后没有任何内容改变地图字段(因为你已经将它命名为“不可变”,我想这就是这个想法)并且在构造期间不会改变作为构造函数参数提供的映射,这是一个线程安全的类。 / p>

请注意,除了迭代之外,还有更简单的方法可以将条目从一个地图复制到另一个地图。您可能还想查看Collections中的一些方法来创建集合的不可变版本。当然,您首先必须复制地图,因为底层地图仍然可以更改。 “不可修改的*”方法仅返回集合的视图

答案 4 :(得分:1)

是和否,对于“线程安全”的不同值(对不起的答案)。构造不是线程安全的,但是一旦构造函数完成,并假设它已经正确发生,它将是不可变的和线程安全的。

在构建过程中,map参数可能会被调用map.entrySetentry.getKeyentry.getValue之间的另一个线程修改。来自Map.entrySet的javadoc。这可能导致未定义的行为。

来自java.util.Map的{​​{1}} javadoc:

  

返回映射的set视图   包含在这张地图中。每个元素   返回的集合是Map.Entry。该   set由地图支持,因此更改   到地图都反映在集合中,   反之亦然。如果地图被修改   而对集合的迭代是在   进度,迭代的结果   未定义。该集支持   元素删除,删除   来自地图的相应映射,   通过Iterator.remove,Set.remove,   removeAll,retainAll和clear   操作。它不支持   添加或添加所有操作。

因此,根据谁有对地图的引用,以及它们如何跨线程操作它,以及复制元素需要多长时间,您有不确定的行为,并且可能在程序的不同执行中有不同的行为。

关于确保从此类中正确构造,似乎没什么可做的(例如,因为entrySet有类似的问题)。如果你的代码的其余部分可以确保给构造函数一个线程安全的映射,那么它也是线程安全的,但这比你提出的范围更宽。

然而,一旦构造函数完成,并且它被安全地发布[1]它将是不可变的和线程安全的 - 尽管只是在浅层 - 如果键或条目是可变的,这 taints 你的班级,既不是一成不变的,也不是线程安全的。因此,如果您的密钥是ConcurrentHashMap并且您的值是String,那么它将是不可变的,因此是线程安全的。但是,如果您的键是一个带有setter的bean类对象,并且您的值是其他可变集合类型,那么您的类不是不可变的。我倾向于将此标记为“乌龟一直向下”状态。

基本上,您的类可能不可变且线程安全,在您的问题未涵盖的某些超范围条件下。

[1]您的类目前将安全发布,但是,例如,如果它成为嵌套类,或者您在构造函数中将Integer引用传递给另一个方法,则这可能会成为不安全的发布。

答案 5 :(得分:0)

在声明字段时使用ConcurrentHashMap代替HashMapConcurrentMap,它应该是。

答案 6 :(得分:0)

类本身 是线程安全的。

类所包含的地图内容不是。客户可以在获得后更改条目。它是否使整个类的线程安全取决于此类与V之间的逻辑关系。

答案 7 :(得分:0)

我认为它的方式是线程安全的。您已创建(有效)输入map的副本,并且仅对其执行只读操作。我总是认为不可变类本质上是线程安全的。但请注意,除非您的KV类是不可变的,否则您仍可能遇到线程安全问题。

您也可以将其替换为:

Map<K, V> m = Collections.unmodifiableMap(Collections.synchronizedMap(map));