5
votes

Java Concurrency in Practice says you can safely publish an effectively immutable object (say, a Date object that you construct and never change again) by sticking it into a synchronized collection like the following (from the book, page 53):

public Map<String, Date> lastLogin =
    Collections.synchronizedMap(new HashMap<String, Date>())

I understand that any Date object put into this map will be visible (at least in its initial but completely constructed state) once placed into this synchronized map, but only once other threads can obtain the reference to this Map object.

Since the reference field lastLogin has none of the properties of fields that guarantee visibility (final, volatile, guarded, or initialized by a static initializer), I think that it's possible the map itself will not show up in a completely constructed state to other threads, therefore putting the cart before the horse. Or am I missing something?

1
By the way… (a) the java.time classes such as Instant, LocalDate, and ZonedDateTime are indeed immutable and thread-safe. So no need to build your own date class. ☺ (b) Avoid the troublesome legacy classes like java.util.Date and Calendar as they are not thread-safe and have many other problems. ☹ - Basil Bourque
I think it works because the syncronized collection invokes a synchronization at some point and this always make the full state visible to other threads. - centic
The assumption is that lastLogin is itself safely published; what he's trying to get at is that once you have the reference to the map, you can use it to safely publish other references. - yshavit

1 Answers

5
votes

Your suspicion is half right, in that the value of lastLogin is not guaranteed to be visible to other threads. Because lastLogin is not volatile or final, another thread may read it as null.

However, you do not need to worry that other threads will see an incomplete version of the map. Collections.synchronizedMap(...) returns an instance of a private class with final fields. JLS section 17.5 says:

The usage model for final fields is a simple one: Set the final fields for an object in that object's constructor; and do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed, then when the object is seen by another thread, that thread will always see the correctly constructed version of that object's final fields.

SynchronizedMap follows these rules, so another thread reading lastLogin will either read null or a reference to the fully constructed map, never a reference to an incomplete or unsafe version of the map.