Java.util.HashMap lock on actual HashMap object compare to lock on object that encapsulate the HashMap

collectionsjava

The below Javadoc is an snippet of HashMap documentation. Why authors would emphasize on putting a lock on the object that encapsulate a HashMap? Lock on the actual HashMap Object makes for sense.

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. If no such object exists, the map should be "wrapped" using the Collections.synchronizedMap method…

Best Answer

Encapsulation is necessary here in order to restrict direct access of client code to methods of HashMap.

As for recommendation on what object to use as a lock, it serves a purpose to allow client code to iterate over the map in a thread safe way.

If you intend your map to be synchronized but lock on actual HashMap object, without hiding (encapsulating) it, then any code having access to that object would be able to invoke its unsynchronized methods and break synchronization. Purpose of encapsulation in this case is stated further in javadocs you refer: "to prevent accidental unsynchronized access to the map".

Documentation also refers to Collections.synchronizedMap as an example on how one is expected to synchronize:

Returns a synchronized (thread-safe) map backed by the specified map. In order to guarantee serial access, it is critical that all access to the backing map is accomplished through the returned map...

You see, synchronizedMap documentation reiterates the importance ("it is critical") for the client code not accessing "backing map" directly.

This documentation also further provides the example explaining why it is recommended to synchronize on the enclosing object:

It is imperative that the user manually synchronize on the returned map when iterating over any of its collection views:

  Map m = Collections.synchronizedMap(new HashMap());
      ...
  Set s = m.keySet();  // Needn't be in synchronized block
      ...
  synchronized (m) {  // Synchronizing on m, not s!
      Iterator i = s.iterator(); // Must be in synchronized block
      while (i.hasNext())
          foo(i.next());
  }

Failure to follow this advice may result in non-deterministic behavior.

You see, in the above code example, if client code would not have access to "known by convention" lock object, it would be impossible to properly synchronize: some other code could have used other lock to wrap the iteration which would lead to unsynchronized access.

Related Topic