There are 2 sub-questions here: visability of reference to a map and visability of values written to the map.
- Visability of the reference to the map:
Do we need to make the map...
You
should care about
safe publication of your references in multi-threading environment. Safe publication means all the values written before the publication are visible to all readers that observe the published object. There are the following few ways to publish a reference safely according to JMM:
- Provide access to the reference through a properly locked field (JLS 17.4.5)
- Use static initializer to do the initializing stores (JLS 12.4) (not our case actually)
- Provide access to the reference via a volatile field (JLS 17.4.5), or as the consequence of this rule, via the AtomicX classes like AtomicReference
- Initialize the value as a final field (JLS 17.5)
So, in your case your "map" reference isn't published correctly. This may cause NullPointerException in Test.read() or/and Test.write() (it depends on which thread instantiates ConcurrentHashMap and puts it into "map" field). Correct code would be one of the following:
//1. Provide access to the reference through a properly locked field
class Test {
ConcurrentHashMap map;
synchronized void init(ConcurrentHashMap map) {
this.map = map;
}
synchronized void read() {
map.get(object);
}
synchronized void write() {
map.put(key, object);
}
}
// or
class Test {
ReadWriteLock rwl = new ReentrantReadWriteLock();
ConcurrentHashMap map;
void init(ConcurrentHashMap map) {
rwl.writeLock().lock();
this.map = map;
rwl.writeLock().release();
}
void read() {
rwl.readLock().lock();
try {
map.get(object);
} finally {
rwl.readLock().release();
}
}
void write() {
rwl.writeLock().lock();
try {
map.put(key, object);
} finally {
rwl.writeLock().release();
}
}
}
// 3. Provide access to the reference via a volatile field
class Test {
volatile ConcurrentHashMap map; // or AtomicReference<ConcurrentHashMap> map = new AtomicReference();
void init(ConcurrentHashMap map) {
this.map = map;
}
void read() {
map.get(object);
}
void write() {
map.put(key, object);
}
}
// 4. Initialize the value as a final field
class Test {
final ConcurrentHashMap map;
Test(ConcurrentHashMap map) {
this.map = map;
}
void read() {
map.get(object);
}
void write() {
map.put(key, object);
}
}
Of course, you can use plain HashMap in case of p.1 (when you work with the properly locked field "map") instead of ConcurrentHashMap. But if you still want to use ConcurrentHashMap for better performance, the best way to publish your "map" correctly, as you see, is to make the field final.
Here is a nice article about safe publication from an Oracle guy, btw:
http://shipilev.net/blog/2014/safe-public-construction/
- Visability of values written to the map:
Is it possible that a put to the map in one thread is not seen or seen
very late by a different thread?
No, if you don't get NPE (see p.1) or have published your map correctly, a reader always sees all changes produced by a writer, because a pair of ConcurrentHashMap.put/get produces appropriate memory barriers/Happens-Before edge.
Same question for HashMap
HashMap isn't thread safe at all. Methods HashMap.put/get work with internal state of the map in not thread-safe manner (non-atomic, no inter-thread visibility of changed state guaranteed), so, you may just corrupt state of the map. This means you must use an appropriate locking mechanism (synchronized sections, ReadWriteLock etc.) to work with HashMap. And, as result of locking, you achieve what you need - a reader always sees all changes produced by a writer because those locks produce memory barriers/Happens-Before edges.