The difference between locking in a Collections.synchronizedMap()
and a ConcurrentHashMap
is as follows:
If multiple threads will access a Collections.synchronizedMap()
frequently, there will be a lot of contention since each method is synchronized using a shared lock (i.e. if thread X calls a method on a Collections.synchronizedMap()
, all other threads will be blocked from calling any method on a Collections.synchronizedMap()
until thread X returns from the method it called).
A ConcurrentHashMap
has a variable number of locks (default is 16) that each guard a segment of the keys in the ConcurrentHashMap
. So for a ConcurrentHashMap
with 160 keys, each lock will guard 10 elements. Therefore, methods operating on a key (get
, put
, set
, etc...) only lock out access to other methods operating on a key where the keys are in the same segment. For example, if thread X calls put(0, someObject)
, and then thread Y calls put(10, someOtherObject)
those calls can execute concurrently, and thread Y does not have to wait for thread X to return from put(0, someObject)
. An example is provided below.
Additionally, certain methods such as size()
and isEmpty()
are not guarded at all. While this allows for greater concurrency, it means they are not strongly-consistent (they won't reflect state that is concurrently changing).
public static void main(String[] args) {
ConcurrentHashMap<Integer, Object> map = new ConcurrentHashMap<>(160);
new Thread(new Runnable() {
@Override
public void run() {
map.put(0, "guarded by one lock");
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
map.put(10, "guarded by another lock");
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
// could print 0, 1, or 2
System.out.println(map.count());
}
}.start();
}