我在实践中阅读java并发。有一些问题,我无法理解。 例如,
package com.thread;
import java.util.Collections;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
public class HiddenIterator {
private final Set<Integer> set = Collections.synchronizedSet(new HashSet<Integer>());
public void add(Integer i) {
synchronized (set) {
set.add(i);
}
}
public void remove(Integer i) {
synchronized (set) {
set.remove(i);
}
}
public void addTenThings() {
Random random = new Random();
for (int i = 0; i < 10; ++i) {
add(random.nextInt());
}
//Hidden Iterator!
System.out.println("DEBUG: added ten elements to " + set);
}
}
程序线程安全吗? 如果不是,如何编辑?
答案 0 :(得分:2)
您对该集合的部分访问权限是线程安全的:对add
和remove
的调用是同步的,因此它们无法同时运行。
但是,在构建消息时,最后的System.out
行会在集合上调用toString
,而toString
必须遍历集合的元素。虽然您已经使用了synchronizedSet
,但它只能保护对各个元素的访问权限 - 但不会在迭代期间保持集合不变。如果其他线程在System.out
行运行时添加和删除元素,则无法预测消息中将显示哪些数字。您需要围绕该行synchronized
阻止&#34;冻结&#34;构建消息时设置的内容。
请注意,只有个人 add
来电是同步的,因此其他线程可以看到&#34;之间的#34;要添加的各个项目。这意味着其他线程可能只看到十个项目中的一些列表。取决于您的程序使用列表的内容,这可能是也可能不是问题。
如果您需要以原子方式添加十个元素,以便其他线程可以看到所有这些元素,或者看不到它们,则可以在synchronized
方法中围绕循环放置addTenThings
块。
您不需要同时使用Collections.synchronizedSet
和 synchronized
块。其中一个还可以。不同之处是:
Collections.synchronizedSet
保护对该集的所有访问权限,因此您无法在需要的地方忘记同步。但是,它只能保护集合上的单个方法调用。特别是,迭代该集会导致不可预测的结果,因为在循环运行时,其他线程可以添加和删除项目。synchronized
块可以保护集合上的多个方法调用,以便它们充当原子操作 - 但它们只能防止其他synchronized
块,因此您必须记住使用{{1}围绕访问该集的所有代码。答案 1 :(得分:1)
有些太安全了,有些不够安全。您不需要在add()
和remove()
内明确同步,因为synchronizedSet
包装器会自动完成同步。
但是,您确实需要围绕println()
语句进行同步,因为当您连接set
时,它会隐式调用set.toString()
,它在内部迭代其元素(&#34;隐藏迭代器&#34;),如果没有明确的同步就不安全,如documentation中所述。