围绕java同步的问题;何时/如何/到何种程度

时间:2011-03-02 11:02:28

标签: java synchronization

我正在研究我的第一个多线程程序,并且遇到了几个同步问题。我已经浏览了oracle / sun主页上的多线程教程,以及SO上的一些问题,所以我相信我对同步是什么有所了解。但是,正如我所提到的,有几个方面我不太清楚如何弄明白。我在下面以明确的问题形式提出它们:

问题1:我有一个单例类,它包含检查有效标识符的方法。事实证明,这个类需要保持集合以跟踪2种不同标识符类型之间的关联。 (如果单词标识符听起来很复杂;这些只是字符串)。我选择实现两个MultiValueMap实例来实现这种多对多关系。我不确定这些集合是否必须是线程安全的,因为集合只会在创建单例类的实例时更新,但我注意到它在文档中说:

  

请注意,MultiValueMap未同步且不是线程安全的。如果您希望同时使用多个线程中的此映射,则必须使用适当的同步。当没有同步的并发线程访问时,此类可能会抛出异常。

有人可以详细说明这种“适当的同步”吗?究竟是什么意思?我无法在同步MultiValueMap.decorate()上使用HashMap,或者我误解了什么?

问题2:我有另一个扩展HashMap的类来保存我的实验值,这些值在软件启动时解析。此类旨在为我的分析提供适当的方法,例如permutation()randomization()filtering(criteria)等。由于我想尽可能保护我的数据,因此创建了类并且更新一次,所有上述方法都返回新的集合。同样,我不确定这个类是否需要是线程安全的,因为它不应该从多个线程更新,但是这些方法肯定会从多个线程调用,并且是“安全的”我添加了我所有方法的synchronized修饰符。你能预见到有什么问题吗?我应该注意哪些潜在的问题?

谢谢,

4 个答案:

答案 0 :(得分:2)

很难给出有关同步的一般规则,但您的一般理解是正确的。以只读方式使用的数据结构不必同步。但是,(1)你必须确保没有人(即没有其他线程)在正确初始化之前可以使用这个结构,并且(2)结构确​​实是只读的。请记住,即使是迭代器也有一个删除方法。

关于你的第二个问题:为了确保不变性,即它是只读的,我不会继承HashMap,而是在你的课堂中使用它。

答案 1 :(得分:2)

答案1:您的单例类不应将其内部使用的集合公开给其他对象。相反,它应该提供适当的方法来公开你想要的行为。例如,如果您的对象中包含Map,则没有公共或受保护的方法来返回Map。而是有一个方法,它接受一个键并返回Map中的相应值(以及可选的一个设置键的值)。如果需要,可以使这些方法成为线程安全的。

即使对于您不打算写入的集合,我也不认为您应该假设读取必须是线程安全的,除非它们被记录为如此。集合对象可能会保留一些您看不到的内部状态,但可能会在读取时被修改。

回答2:首先,我不认为继承在这里使用是正确的。我会有一个提供您方法的类,并且有一个HashMap作为私有成员。只要您的方法不更改对象或HashMap的内部状态,就不必同步它们。

答案 2 :(得分:1)

如果你的地图被填充一次,那么在加载类时(即在静态初始化程序块中),并且之后永远不会修改(即没有添加/删除任何元素或关联),你就可以了。保证静态初始化由JVM以线程安全的方式执行,并且其结果对所有线程都可见。所以在这种情况下,你很可能不需要任何进一步的同步。

如果地图是实例成员(我的描述中对我不清楚),但在创建后没有修改,我会再说一遍,如果你宣布你的成员final,你很可能是安全的(除非你发表this对象引用过早,即在构造函数完成之前以某种方式从构造函数传递给外部世界。)

答案 3 :(得分:1)

当您可以同时修改基础数据或者一个线程修改数据而另一个线程读取并需要查看修改时,通常需要同步。

在您的情况下,如果我理解正确,MultiValueMap会在创建时填充一次并且刚刚读取。因此,除非读取地图会修改某些内部结构,否则在没有同步的情况下从多个线程读取它应该是安全的。创建过程应该是同步的,或者你应该至少在初始化期间阻止读取访问(一个简单的标志可能就足够了)。

如果您总是返回新的集合,并且在创建这些“副本”期间没有修改基本集合的内部,那么您在问题2中描述的类可能不需要同步。

还有一点需要注意:请注意集合中的值可能也需要同步,因为如果你安全地从多个线程中的集合中获取一个对象,然后同时修改该对象,你仍然会得到问题。

因此,作为一般经验法则:只读访问不一定需要同步(如果在这些读取期间没有修改对象或者无关紧要),通常应该同步写访问。