在JCIP中,我们有一段代码如下:
清单4.2:
@ThreadSafe
public class PersonSet {
@GuardedBy("this")
private final Set<Person> mySet = new HashSet<Person>(); // line 3
public synchronized void addPerson(Person p) {
mySet.add(p);
}
public synchronized boolean containsPerson(Person p) {
return mySet.contains(p);
}
}
我想知道我们是否将第三行更改为:
private Set<Person> mySet = new HashSet<Person>(); // line 3, removes final
是否真的说该类不再是线程安全的,因为即使在构造函数退出并且发布了对PersonSet实例的引用之后,非最终变量mySet
也可能为null?
例如,这样的调用代码可能会失败,还是我误解了某些事情,这是真的吗? :
PersonSet p = new PersonSet();
SendToThreadB(p);
如果我有一个限制,不允许将字段标记为“final”(如我可能必须将其与新实例交换),那么有什么解决方案可以确保该类仍然是线程安全的使用final
?
答案 0 :(得分:2)
如果您希望能够更改字段值,请创建字段volatile
,如果您希望对该字段的更改对其他线程可见,而不必使用其他发生在机制。 (读取和写入volatile
字段发生在事件之前。)
当然,如果您总是使用其他发生在之前的系统(例如,synchronized
,互斥锁,BlockingQueue
,Exchanger
,则设置字段值在创建您发送给它的线程之前,等等,然后volatile
没有必要。但这更加脆弱,因为如果你以后更改代码,以便在发生之前没有发生,那么你就会创建一个bug。
答案 1 :(得分:2)
如果删除final
,则在 unsafe publication 之后使用实例时会变得不安全。也就是说,如果另一个线程无法通过synchronized
/ volatile
访问该对象以生成适当的发生在之前的关系,那么它可能会看到部分初始化的对象。在这种情况下,它可能会以相对安全的方式失败,从NullPointerException
取消引用mySet
字段。理论上,另一个线程可以看到对HashSet
的引用,但该对象的部分或全部字段可能尚未设置。
答案 2 :(得分:1)
如果删除final
,由于方法上的synchronized
修饰符,该类仍然是线程安全的。他们将提供发生之前的关系,以保证每个人都能看到mySet
引用的相同值。
关于final
的观点是,它使这个变体对于类ifself而言是线程安全的。
public class PersonSet {
private final Set<Person> mySet = new HashSet<Person>();
public Set<Person> getSet() {
return mySet;
}
}
请注意,getter上没有synchronized
修饰符。
(当然,任何获取HashSet
对象的代码都需要在对象上同步它们的操作......不知何故......这意味着这不是一个好设计的例子。)
答案 3 :(得分:0)
我认为你是对的 - 你需要final
关键字,没有它,另一个线程可以将mySet视为null
。
除了final
字段之外,构造函数没有内存障碍或同步。因此,尽管对HashSet的各种修改都受到同一个监视器(属于this
的监视器)的保护,但是该对象对mySet字段的引用的分配没有受到保护。