【问题标题】:In Java, do I need to declare my collection synchronized if it's read-only?在Java中,如果我的集合是只读的,我是否需要声明我的集合是同步的?
【发布时间】:2010-09-14 05:15:52
【问题描述】:

当我的 J2EE webapp 启动时,我会一次性填充一个集合。 然后,多个线程可以同时访问它,但只是为了读取它。

我知道并行写入必须使用同步集合,但并行读取仍然需要它吗?

【问题讨论】:

    标签: java multithreading synchronization web-applications


    【解决方案1】:

    这取决于读取您的集合的线程是在您填充它之前还是之后启动的。如果它们在您填充它之前启动,您无法保证(没有同步)这些线程将永远看到更新的值。

    原因是 Java 内存模型,如果您想了解更多信息,请阅读此链接的“可见性”部分:http://gee.cs.oswego.edu/dl/cpj/jmm.html

    即使在填充集合后启动线程,您也可能需要同步,因为即使在读取操作时,集合实现也可能更改其内部状态(感谢 Michael Bar-Sinai,我不知道此类集合存在于标准JDK)。

    关于并发主题的另一本非常有趣的读物是 Brian Goetz 的书 Java Concurrency in Practice

    【讨论】:

    • 感谢您的回答,但是使用“volatile”可以帮助获取其他线程中的最新值吗?
    【解决方案2】:

    通常不会,因为在这种情况下您不会更改集合的内部状态。当您对集合进行迭代时,会创建一个新的迭代器实例,并且迭代的状态是每个迭代器实例。


    旁注:请记住,保留只读集合只是防止对集合本身进行修改。每个集合元素仍然是可变的。

    class Test {
        public Test(final int a, final int b) {
            this.a = a;
            this.b = b;
        }
    
        public int a;
        public int b;
    }
    
    public class Main {
    
        public static void main(String[] args) throws Exception {
            List<Test> values = new ArrayList<Test>(2);
            values.add(new Test(1, 2));
            values.add(new Test(3, 4));
    
            List<Test> readOnly = Collections.unmodifiableList(values);
            for (Test t : readOnly) {
                t.a = 5;
            }
    
            for (Test t : values) {
                System.out.println(t.a);
            }
        }
    
    }
    

    这个输出:

    5
    5
    

    @WMR answser 的重要注意事项。

    这取决于线程是否 开始阅读您的收藏 在填充之前或之后。如果 他们在你填满它之前就开始了, 你没有任何保证(没有 同步),这些线程 将永远看到更新的值。

    原因是 Java 内存 型号,如果您想了解更多信息,请阅读 此链接的“可见性”部分: http://gee.cs.oswego.edu/dl/cpj/jmm.html

    即使线程已启动 在你填满你的收藏后,你 可能必须同步,因为您的 收集实施可能会改变 即使在读取时它的内部状态 运营(感谢Michael Bar-Sinai, 我不知道这样的收藏 存在)。

    另一个非常有趣的阅读 涵盖的并发主题 诸如发布对象之类的主题, 能见度等更详细 是 Brian Goetz 的书Java Concurrency in Practice

    【讨论】:

    • 这个答案没有错,但也不够。 @WMR 的回答有一些额外的重要警告。特别是,您需要考虑确保读取器线程实际上会通过安全发布或某些内存屏障看到在启动时写入的值
    • 请参阅下面的@WMR 和我的答案...这个答案是错误的,因为 get() 操作可能会改变集合中的内部状态(例如缓存、上次访问的统计信息等)。
    • @Alex Miller:我认为答案不仅不充分,而且在没有列出任何条件的情况下明确表示“不”。这是错误的(如果你依赖它,你可能知道很危险)。
    • 您还具有额外的复杂性,即可以使用有状态或非线程安全对象来实现集合。当处理某些延迟加载对象的 ORM/JPA 集合时,可能会发生这种情况。
    【解决方案3】:

    在一般情况下,您应该这样做。这是因为某些集合在读取期间会更改其内部结构。使用访问顺序的 LinkedHashMap 就是一个很好的例子。但不要只相信我的话:

    在access-ordered linked hash maps中,仅仅用get查询map是一种结构修改 The Linked hash map's javadoc

    如果您完全确定没有缓存、没有收集统计​​信息、没有优化、根本没有有趣的东西 - 您不需要同步。在那种情况下,我会对集合设置类型约束:不要将集合声明为 Map(这将允许 LinkedHashMap),而是声明为 HashMap(对于纯粹主义者来说,是 HashMap 的最终子类,但这可能也是远...)。

    【讨论】:

    • 有趣.. 从来不知道某些集合在读取过程中会改变其内部结构。
    【解决方案4】:

    如其他答案中所述,您不必这样做。如果你想确保你的收藏是只读的,你可以使用:

    yourCollection = Collections.unmodifableCollection(yourCollection);
    

    (List、Set、Map等集合类型也有类似的方法)

    【讨论】:

      【解决方案5】:

      集合本身不是,但请记住,如果它所拥有的也不是不可变的,那么这些单独的类需要它们自己的同步。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-06-29
        • 1970-01-01
        • 2011-02-07
        • 1970-01-01
        • 2017-09-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多