Locking a ConcurrenthashMap for exclusive access.

A friend recently pointed me to an acticle on ConcurrentHashMap which indicated to him that you can't lock a ConcurrentHashMap for exclusive access. This came as a surprise to me as I have been doing just that for years.

Hashtable and Collections.synchronizedMap achieve thread safety by
synchronizing every method. This means that when one thread is executing one
of the Map methods, other threads cannot until the first thread is finished,
regardless of what they want to do with the Map.

In most cases, ConcurrentHashMap is a drop-in replacement for Hashtable or
Collections.synchronizedMap(new HashMap()). However, there is one
significant difference -- synchronizing on a ConcurrentHashMap instance does
not lock the map for exclusive use. In fact, there is no way to lock a
ConcurrentHashMap for exclusive use -- it is designed to be accessed
concurrently.

https://www6.software.ibm.com/developerworks/education/j-concur/j-concur-a4.pdf

This appears confused to me. On the one hand it states that synchronizing every method is one solution to providing an exclusive lock but it also states its not possible with ConcurrentHashMap (CHM), even though it is just as possible to lock every method of CHM and just as effective.

The downside of locking every method in a CHM is that you lose its main benifit; the ability to have concurrent reads and writes. However, this doesn't mean it wouldn't work, or that its not possible. You would still have an iterator which wouldn't throw a ConcurrentModificationException (a second benifit)

There are ways around this limitation. One way around this problem is to allow concurrent reads but not current writes. This works well if you have a high read to write ratio. This can be achieved by locking all write operations but not the reads and will work in most situations, provided only one key is altered.


public class CMap<K, V> {
private final ConcurrentMap<K, V> map = new ConcurrentHashMap<K, V>();
private final Object[] locks = new Object[127];

public V put(K key, V value) {
synchronized (map) { // exclusive write acess.
return map.put(key, value);
}
}

public V get(K key) {
return map.get(key); // thread safe read.
}

public V putIfAbsentTheHardWay(K key, V value) {
synchronized (map) { // exclusive write acess.
if (!map.containsKey(key))
return map.put(key, value);
else
return map.get(key);
}
}
}


However, say you want to allow concurrent writes as well, but you need an operation not supported by CHM.

By having an array of locks you can have both concurrent reads and writes and still have you own locking strategy.

public class CMap<K, V> {
private final ConcurrentMap<K, V> map = new ConcurrentHashMap<K, V>();
private final Object[] locks = new Object[16]; {
for(int i = 0;i<locks.length;i++) locks[i] = new Object();
}

public V put(K key, V value) {
int hash = key.hashCode() & 0x7FFFFFFF;
synchronized (locks[hash % locks.length]) { // allows concurrent writes.
return map.put(key, value);
}
}

public V get(K key) {
return map.get(key); // concurrent reads.
}

public V putIfAbsentTheHardWay(K key, V value) {
int hash = key.hashCode() & 0x7FFFFFFF;
synchronized (locks[hash % locks.length]) { // supports custom operations.
if (!map.containsKey(key))
return map.put(key, value);
else
return map.get(key);
}
}
}

Comments

  1. I doubt your last approch with the arrays of locks works. Well, it will work in the sense that it runs correctly but it will not provide any better concurrency than the single lock approach. The ConcurrentHashMap has its own "array" of locks and uses a different segmentation for associating hash values and locks which strongly depends on its implementation (actually it does not uses the key's hashcode directly).

    ReplyDelete
  2. Hi,

    have you tried the code and scenario you mentioned above , did it worked as expected ?

    Javin
    Use of ConcurrentHashMap

    ReplyDelete
  3. @Micha's comment that you won't get the level of concurrency you might expect for writes as two lock have to be acquired. I have used it for an average of four concurrent writers and it worked ok. I suspect it would have issues scaling.

    ReplyDelete

Post a Comment

Popular posts from this blog

Java is Very Fast, If You Don’t Create Many Objects

Low Latency Microservices, A Retrospective

Unusual Java: StackTrace Extends Throwable