Reputation: 15479
Will threads that call IsItThreadsafe.getValue(...)
get an up-to-date value from the Map referenced by the variable IsItThreadsafe.map
(assuming the exact usage scenario implemented in IsItThreadSafe.main(...)
?
public class IsItThreadSafe {
private Map<String, String> map;
public synchronized void setMap(Map<String, String> map) {
this.map = map;
}
public synchronized String getValue(String key) {
if (this.map == null) {
this.map = new HashMap<>();
}
return map.get(key);
}
public static void main(String[] args) throws Exception {
IsItThreadSafe sharedObject = new IsItThreadSafe();
Thread t1 = new Thread(() -> {
while (true) {
for (int i = 0; i < 10; i++) {
String key = String.valueOf(i);
String value = sharedObject.getValue(key);
if (value!=null) {
System.out.println("key = " + key + " value = " + value);
}
}
}
});
t1.start();
Thread t2 = new Thread(() -> {
while (true) {
Map<String, String> mappedData = new HashMap<>();
for (int i = 0; i < 10; i++) {
mappedData.put(String.valueOf(i), String.valueOf(i + 1));
}
sharedObject.setMap(mappedData);
}
});
t2.start();
t1.join();
t2.join();
}
}
Upvotes: 0
Views: 52
Reputation: 100239
Seems that it should work correctly unless the setMap
caller continues to modify the map it passed to setMap
. It should forget about this map once the setMap
is called. Such version would be more safe in general:
public synchronized void setMap(Map<String, String> map) {
this.map = new HashMap<>(map);
}
Of course though your code is correct, it doesn't mean that the code is optimal. In particular if you are not going to put new values into the map, it's possible to get rid of synchronization every time you query the value. For example, you may use AtomicReference
:
private final AtomicReference<Map<String, String>> map = new AtomicReference<>();
public void setMap(Map<String, String> map) {
this.map.set(new HashMap<>(map));
}
public String getValue(String key) {
Map<String, String> m = this.map.get();
// loop is necessary if setMap(null) call occurs
while (m == null) {
// Use CAS to avoid possible overwrite of concurrent setMap() call
m = new HashMap<>();
if(!this.map.compareAndSet(null, m))
m = this.map.get();
}
return m.get(key);
}
Here synchronization is replaced with volatile read which is in general much faster.
Upvotes: 1