Should we always use a ConcurrentHashMap when using multiple threads? - java

If I have a hash map and this method:
private Map<String, String> m = new HashMap<>();
private void add(String key, String value) {
String val = m.get(key);
if (val == null) {
m.put(key, value);
}
}
If I have two threads A and B calling the method with the same key and value, A and B may both see that the key is not in the map, and so may both write to the map simultaneously. However, the write order (A before B or B before A) should not affect the result because they both write the same value. But I am just wondering whether concurrent writes would be dangerous and could lead to unexpected results. In that case I should maybe use a ConcurrentHashMap.

Yes, you should use a ConcurrentHashMap (which is internally thread-safe), and use the m.putIfAbsent(key, value) of it.
m should also be final, to avoid that it is being reassigned.

Related

ConcurrentHashMap, find by value, compare fields and put

How can I check if there is a value using the fields of a given value? And put new one?
In ConcurrentHashMap, cause I have N threads.
Here is an example of what I want. However, it is not thread-safe.
Map<Integer, Record> map = new ConcurrentHashMap<>();
// it works, but I think it's unsafe
int get(Object key) {
for (Map.Entry<Integer, Record> next : map.entrySet()) {
if (next.getValue().a == key) {
return next.getValue().b;
}
}
int code = ...newCode();
map.put(code, new Record(...))
return code;
}
record Record(Object a, int b) {
}
What you're suggesting would defeat the purpose of using a HashMap since you're iterating through the Map instead of retrieving from the Map.
What you should really do is create a new Map where the field in Record.a is the Key and the field in Record.B is the value (or just the whole Record). Then just update your logic to insert into both Maps appropriately.

About dual key concurrent hashmap in java

I need dual key concurrent hashmap.
My first try is just using java.util.concurrent.ConcurrentHashMap. Like this
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key1" + "|" +"key2", "value");
String vaule = map.get("key1" + "|" +"key2");
but I think this is ugly.
My Second try is using Object as Key. Like this
#Data
public class DualKey {
private final String key1;
private final String key2;
}
map.put(new DualKey("key1", "key2"), "value");
String vaule = map.get(new DualKey("key1", "key2"));
Last try is create DualkeyConcurrentHashMap. I just need put, get, containsKey.
public class DualkeyConcurrentHashMap<K1, K2, V> {
private final ConcurrentHashMap<K1, ConcurrentHashMap<K2, V>> map
= new ConcurrentHashMap<>();
public V put(K1 key1, K2 key2, V value) {
ConcurrentHashMap<K2, V> subMap
= map.computeIfAbsent(key1, k -> new ConcurrentHashMap<>());
return subMap.put(key2, value);
}
public V get(K1 key1, K2 key2) {
ConcurrentHashMap<K2, V> subMap = map.get(key1);
return null == subMap ? null : subMap.get(key2);
}
public boolean containsKey(K1 key1, K2 key2) {
return null != get(key1, key2);
}
}
Is it better and perfectly thread safe?
(I can't decide all method need synchronized.)
Is there another recommended way?
All options are thread-safe, which is guaranteed by ConcurrentHashMap. Important fact to note is:
However, even though all operations are thread-safe, retrieval
operations do not entail locking, and there is not any support for
locking the entire table in a way that prevents all access. This class
is fully interoperable with Hashtable in programs that rely on its
thread safety but not on its synchronization details.
The natural way to implement a dual key map would be to provide an object, so I would go with the second one, only that I would make DualKey generic.
The first one couples implementation and design (string1 "|" + string1 key format) and does not allow you to change types used as keys easily.
The third one uses much more instances of ConcurrentHashMap than needed.

Double Locking on Normal Map - Is this Safe?

The majority of access would be reads and the map itself will be relatively small. That said would the following be safe, my tests show that the results are faster than a ConcurrentHashMap (at least on 1.7x).
volatile Map<String, Object> mapV;
public Object getV(String key) {
Object value = mapV.get(key);
if (value == null) {
synchronized(this) {
value = mapV.get(key);
if (value == null) {
value = new Object();
Map<String, Object> copy = new HashMap<String, Object>();
copy.putAll(mapV);
copy.put(key, value);
mapV = Collections.unmodifiableMap(copy);
}
}
}
return value;
}
This would be thread-safe. Since your map assignments are volatile any thread which sees mapV.get(key) != null will be hitting a volatile read and so would be safe.
As an edit: I would be surprised if this is actually faster or if it would be faster with a lot of data. I would definitely use a ConcurrentHashMap instead of this implementation.

Is changing the value of a Map an atomic operation?

I was wondering if synchronization or using a concurrent class is necessary, or conversely is it thread safe to use a non concurrent class and do no synchronization on a map in a multi threaded environment, if the only modification to the map is changing the values of the map.
The reason I ask this is the HashMap ( and other non concurrent maps documentation ) have this comment:
Note that this implementation is not synchronized.
If multiple threads access a hash map concurrently, and at least one of
the threads modifies the map structurally, it must be
synchronized externally. (A structural modification is any operation
that adds or deletes one or more mappings; merely changing the value
associated with a key that an instance already contains is not a
structural modification.) This is typically accomplished by
synchronizing on some object that naturally encapsulates the map.
Which makes me believe if the modification is not structural (i.e. There is no added or deleted) I should be able to update the (non concurrent) map sans synchronization.
Am I reading this correct? i.e. Is Updating of a value in a map an atomic process?
Updating a map value is not an atomic process. However, having multiple different threads each try to modify map values concurrently will not result in very strange exceptions or errors due to concurrency errors. For example, you won't cause one of the key/value pairs to disappear, or delete random elements out of the map.
However, the updates made by one thread when updating a key/value pair will not necessarily be visible to other threads unless there is some other synchronization going on (for example, if the values are things like AtomicIntegers). On top of this, there's no guarantee that the thread will even see its own updates, since they might get clobbered by some other thread.
Hope this helps!
Putting something in a HashMap is not an atomic operation:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
It may be worthwhile to wrap your HashMap with Collections#synchronizedMap.
Implementations of map such as HashMap, TreeMap, etc are not atomic nor thread safe when it comes to updates, but you may achieve atomic update operations when you're using a ConcurrentHashMap since Java 1.8.
The following method for example will add the value for a specific key or set the value if no previous value was available for the key.
ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
int addValue(String key, int value) {
return map.compute(key, (k, v) -> v == null ? value : v + value);
}

Is a Guava Table thread safe when its backing maps are thread safe?

Will Guava's Tables.newCustomTable(Map, Supplier) method return thread safe tables when supplied with thread safe maps? For example:
public static <R, C, V> Table<R, C, V> newConcurrentTable() {
return Tables.newCustomTable(
new ConcurrentHashMap<R, Map<C, V>>(),
new Supplier<Map<C, V>>() {
public Map<C, V> get() {
return new ConcurrentHashMap<C, V>();
}
});
}
Does that code actually return concurrent tables?
From the doc: "If multiple threads access this table concurrently and one of the threads modifies the table, it must be synchronized externally."
Concurrent backing collections aren't enough.
Kevin Bourrillion is right. The technical reason for the map you've constructed not to be thread safe is that even if the maps you are using are thread safe, the table operations may not be. Let me give an example of put, as implemented in the StandardTable, which is used by Tables.newCustomTable:
public V put(R rowKey, C columnKey, V value) {
Map<C, V> map = backingMap.get(rowKey);
if (map == null) {
map = factory.get();
backingMap.put(rowKey, map);
}
return map.put(columnKey, value);
}
Thread safety is compromised in the handling of the map == null case. Namely, two or more threads could enter that block and create a new entry for the columnKey and the last one to perform a backingMap.put(rowKey, map) would ultimately override the entry for the columnKey in the backingMap, which would lead to the loss of put operations performed by other threads. In particular the result of this operation in a multithreaded environment is non-deterministic, which is equivalent to saying that this operation is not thread safe.
The correct implementation of this method would be:
public V put(R rowKey, C columnKey, V value) {
ConcurrentMap<C, V> map = table.get(rowKey);
if (map == null) {
backingMap.putIfAbsent(rowKey, factory.get());
}
map = backingMap.get(rowKey);
return map.put(columnKey, value);
}
I'm currently investigating if it is possible to use the ForwardingTable implementation together with what you've wanted to do, to get a properly thread safe ConcurrentTable.
But to be honest, I think the reason there is no thread-safe implementation of the Table is that the interface itself doesn't provide any concurrency constructs, such as putIfAbsent or replace.

Categories