Reputation: 89743
In JCIP we have a piece of code which looks like this:
Listing 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);
}
}
I was wondering if we change the third line to this:
private Set<Person> mySet = new HashSet<Person>(); // line 3, removes final
Is it true to say that the class is no longer thread-safe because the non-final variable mySet
could have been null, even after the constructor exits and a reference to the PersonSet instance is published?
For example, is it true to say that a calling code like this may fail, or am I misunderstanding somthing? :
PersonSet p = new PersonSet();
SendToThreadB(p);
What if I have a restriction that does not allow the field to be marked "final" (like I may have to swap it with a new instance), what solutions are there to ensure that the class is still thread-safe without using final
?
Upvotes: 1
Views: 470
Reputation: 719336
If you remove the final
, the class is still thread-safe because of the synchronized
modifier on the methods. They will provide the happens-before relationship that guarantees that everyone will see the same value for the mySet
reference.
The point about the final
is that it makes this variant thread-safe with respect to the class ifself.
public class PersonSet {
private final Set<Person> mySet = new HashSet<Person>();
public Set<Person> getSet() {
return mySet;
}
}
Note that there is no synchronized
modifier on the getter.
(Of course, any code that gets hold of the HashSet
object needs to synchronize their operations on the object ... somehow ... which means that this is NOT an example of good design.)
Upvotes: 1
Reputation: 147154
If you remove the final
, instances become unsafe when used after unsafe publication. That is, if another thread gets access to the object without going through synchronized
/volatile
to produce an appropriate happens-before relationship then it may see a partially initialised object. In this case it would probably fail in a relatively safe way giving a NullPointerException
on dereferencing the mySet
field. In theory, another thread could see the reference to the HashSet
, but some or all of the fields of that object may not have been set.
Upvotes: 2
Reputation: 43401
I think you're right -- you need the final
keyword, and without it, another thread could see mySet as null
.
Constructors have no memory barriers or synchronization, other than for final
fields. So even though the various modifications to the HashSet are all guarded by the same monitor (the one that belongs to this
), the assignment of that object's reference to the mySet field is not guarded.
Upvotes: 0
Reputation: 223153
If you want to be able to change the field value, make the field volatile
if you want changes to the field to be visible to other threads without having to use other happens-before mechanisms. (Reading and writing a volatile
field are happens-before events.)
Of course, if you always use other happens-before systems (e.g., synchronized
, mutex locks, BlockingQueue
, Exchanger
, setting the field value before creating the thread you're sending it to, etc.), then volatile
isn't necessary. But that is more fragile, because if you later change code such that no happens-before happens any more, then you've created a bug.
Upvotes: 2