当我的 J2EE webapp 启动时,我会一次性填充一个集合。然后,几个线程可以同时访问它,但只是为了读取它。
我知道并行写入必须使用同步集合,但并行读取仍然需要它吗?
当我的 J2EE webapp 启动时,我会一次性填充一个集合。然后,几个线程可以同时访问它,但只是为了读取它。
我知道并行写入必须使用同步集合,但并行读取仍然需要它吗?
通常不会,因为在这种情况下您不会更改集合的内部状态。当您对集合进行迭代时,会创建一个新的迭代器实例,并且迭代的状态是每个迭代器实例。
旁注:请记住,通过保留只读集合,您只会阻止对集合本身的修改。每个集合元素仍然是可变的。
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,它更详细地涵盖了对象发布、可见性等主题。
这取决于正在读取您的集合的线程是在您填充它之前还是之后启动的。如果它们在填充之前启动,则无法保证(没有同步)这些线程将永远看到更新的值。
原因是 Java 内存模型,如果您想了解更多信息,请阅读此链接上的“可见性”部分:http: //gee.cs.oswego.edu/dl/cpj/jmm.html
即使在填充集合后启动线程,您也可能需要同步,因为即使在读取操作时,集合实现也可能更改其内部状态(感谢Michael Bar-Sinai,我不知道标准 JDK 中存在此类集合)。
关于并发主题的另一本非常有趣的读物是 Brian Goetz 的书Java Concurrency in Practice,它更详细地涵盖了对象发布、可见性等主题。
在一般情况下,你应该。这是因为某些集合在读取期间会更改其内部结构。使用访问顺序的 LinkedHashMap 就是一个很好的例子。但不要只相信我的话:
在访问排序的链接哈希映射中,仅使用 get 查询映射是一种结构修改 链接哈希映射的 javadoc
如果您绝对确定没有缓存、没有收集统计信息、没有优化、根本没有有趣的东西 - 您不需要同步。在那种情况下,我会对集合进行类型约束:不要将集合声明为 Map(这将允许 LinkedHashMap),而是声明为 HashMap(对于纯粹主义者来说,是 HashMap 的最终子类,但这可能也是远的...)。
如其他答案所述,您不必这样做。如果你想确保你的收藏是只读的,你可以使用:
yourCollection = Collections.unmodifableCollection(yourCollection);
(List、Set、Map等集合类型也有类似的方法)
集合本身不是,但请记住,如果它所拥有的也不是不可变的,那么这些单独的类需要它们自己的同步。