【问题标题】:Is static initialized unmodifiableCollection.get guaranteed immutable?静态初始化的 unmodifiableCollection.get 是否保证不可变?
【发布时间】:2010-01-15 20:21:04
【问题描述】:

静态初始化的 unmodifiableCollection.get 是否保证不可变?

为:

静态最终地图 FOO = Collections.unmodifiableMap(new HashMap());

可以多线程使用get方法而不遇到问题吗?

即使 FOO 中的项目无法添加/删除,是什么阻止了 get 方法操作 FOO 的内部状态以进行缓存等。如果以任何方式修改内部状态,则 FOO 不能同时使用。如果是这样的话,java中真正的不可变集合在哪里?

【问题讨论】:

  • 其实是个好问题。想想WeakHashMap - 可以改变而不改变。 LinkedHashMap在访问顺序模式下是一样的。
  • (您可能希望将问题标题复制到问题中 - 横幅盲区。)
  • 作者有个很好的问题。假设我编写了自己的地图实现。让我们假设在每次查找时,它都会重新组织其内部结构,以便下次更快地找到查找次数最多的项目。用户用不可修改的集合包装所述映射并假设它是线程安全的。然后 2 个线程同时访问不同的值,内部优化器搞砸了地图的内部状态。
  • z5h:如果你小心点,你可以吃掉你的蛋糕。我曾经写过一张专家图(用于ThreadLocal 实现),它没有为每个桶提供一个单链表,而是有一个单链循环。如果您错过了命中循环链接中的条目的快速路径,它会将对命中条目的引用写回到数组中。碰巧这是线程安全的。

标签: java collections immutability


【解决方案1】:

举个具体例子:

static final Map FOO = Collections.unmodifiableMap(new HashMap());

那么 FOO 将是不可变的。它也永远不会有任何元素。鉴于更一般的情况:

static final Map BAR = Collections.unmodifiableMap(getMap());

那么这是否不可变完全取决于其他人是否可以访问底层 Map,以及它是什么类型的 Map。例如,如果它是一个 LinkedHashMap,那么底层链表可以通过访问顺序进行修改,并且可以通过调用 get() 进行更改。最安全的方法(使用非并发类)是:

static final Map BAR = Collections.unmodifiableMap(new HashMap(getMap()));

javadocs for HashMap 暗示只要您不对地图进行结构性更改,那么同时使用它是安全的,因此这对于您可以使用的任何访问器都应该是安全的,即获取各种集合并对其进行迭代,然后 get() 应该是安全的。

如果你可以使用并发类,那么你也可以这样做:

static final Map BAR = Collections.unmodifiableMap(new ConcurrentHashMap(getMap());

这对于多线程使用是明确安全的,因为 ConcurrentHashMap 是明确的多线程访问安全的。内部状态可能是可变的,但外部可见状态不会,而且由于该类保证是线程安全的,我们可以放心地认为它是外部不可变的。

【讨论】:

  • 如果我想迭代BAR怎么办?
  • @Tom:正如你在回答中提到的,我现在也在我的回答中提到 ;-),遍历地图应该和使用 get 一样安全。
  • 但是你说的是 ConcurrentHashMapMaps 的未指定实现。
  • 根据定义,ConcurrentHashMap 可以安全地从多个线程中使用。而且,我不是在谈论使用未指定的映射,这就是为什么我创建一个新的 HashMap 以明确地传递给 unmodifiableMap(),以便我们知道它是一个 HashMap,而不是别的什么。
【解决方案2】:

冒着听起来像是在疯狂宣传的风险,请使用Google Immutable Collections 并完成它。

【讨论】:

  • 为什么要无缘无故地引入这样的依赖?
  • 为什么在别人已经拥有的情况下越过障碍来确保不变性?
  • Tom,有些人只是在风格上偏爱public static final Map<Foo, Bar> MAP = ImmutableMap.of(foo1, bar1, foo2, bar2); 而不是public static final Map<Foo, Bar> MAP = initMap(); private static Map<Foo, Bar> initMap() { Map<Foo, Bar> map = new HashMap<Foo, Bar>(); map.put(foo1, bar1); map.put(foo2, bar2); return Collections.unmodifiableMap(map); }
【解决方案3】:

其实是个好问题。想想WeakHashMap - 它可以在没有调用突变操作的情况下改变。访问顺序模式下的LinkedHashMap 大致相同。

HashMap 状态的 API 文档:

请注意,此实现不是 同步。 如果多个线程 同时访问哈希映射,并且在 至少有一个线程修改了 在结构上映射,它必须是 外部同步。 (一个结构 修改是任何操作 添加或删除一个或多个映射; 仅仅改变相关的值 使用实例已经存在的密钥 contains 不是结构性的 修改。)

大概应该是当且仅当。这意味着如果 HashMap 是“有效不可变的”,则 get 不需要同步。

【讨论】:

  • Mr/Ms Downvoter:我的回答有什么问题,还是只是报复?
【解决方案4】:

Java SDK 中没有真正的不可变映射。 Chris 建议的所有地图都是线程安全的。不可修改的 Map 也不是不可变的,因为如果底层 Map 发生变化,ConcurrentModificationException 也会发生变化。

如果您想要真正不可变的地图,请使用 Google Collections / Guava 中的 ImmutableMap。

【讨论】:

    【解决方案5】:

    我建议任何线程操作都使用 ConcurrentHashMap 或 HashTable,两者都是线程安全的。

    【讨论】:

    • 是的,对不可变数据使用并发操作可能是个坏主意。
    【解决方案6】:

    返回的 map 上的 getter 是否碰巧与某些内部状态一起旋转并不重要,只要对象遵守其合同(即是一个无法修改的 map)。所以你的问题是“找错树了”。

    在您对它所包裹的地图没有所有权和控制权的情况下,您应该谨慎对待 UnmodifiableMap。例如

    Map<String,String> wrapped = new HashMap<String,String>();
    wrapped.add("pig","oink");
    Map<String,String> wrapper = Collections.unmodifiableMap(wrapped);
    System.out.println(wrapper.size());
    wrapper.put("cow", "moo"); // throws exception
    wrapped.put("cow", "moo");
    System.out.println(wrapper.size()); // d'oh!
    

    【讨论】:

    • “不重要”——不正确!如果该类不是线程安全的并且两个线程相互踩踏,这一点非常重要!
    • 再读一遍,先生。 “只要它遵守合同。”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-17
    • 1970-01-01
    • 2017-11-10
    • 1970-01-01
    • 2015-06-03
    • 1970-01-01
    • 2011-08-22
    相关资源
    最近更新 更多