Learnitweb

Difference between Hashtable and Collections.synchronizedMap

Both provide thread-safe Map implementations, but they differ in several key aspects:

Origin and Legacy

  • Hashtable: Is a legacy class, present since JDK 1.0. It predates the Java Collections Framework. While it was retrofitted to implement the Map interface, its original design choices remain.
  • Collections.synchronizedMap(): Is a factory method introduced with the Java Collections Framework (JDK 1.2). It’s designed to provide thread-safety for any existing Map implementation by wrapping it.

Synchronization Mechanism

  • Hashtable: Achieves thread safety by synchronizing every public method (like put(), get(), remove(), size(), containsKey(), etc.) on the Hashtable object itself. This is known as coarse-grained locking. Only one thread can execute any method on the Hashtable instance at a time.
  • Collections.synchronizedMap(): Returns a wrapper map. When you call methods on this wrapper (e.g., wrapper.put(), wrapper.get()), the wrapper synchronizes on an internal mutex object (often the wrapper object itself) before delegating the call to the underlying map. This is also coarse-grained locking, similar in effect to Hashtable. Only one thread can execute a method via the wrapper at a time.

Null Keys and Values

  • Hashtable: Does not allow null keys or null values. Attempting to insert either will result in a NullPointerException.
  • Collections.synchronizedMap(): The allowance of null keys/values depends entirely on the underlying map that was wrapped.
    • If you wrap a HashMap (Collections.synchronizedMap(new HashMap<>())), it will allow one null key and multiple null values (because HashMap does).
    • If you wrap a TreeMap (Collections.synchronizedMap(new TreeMap<>())), it generally won’t allow null keys (unless a custom comparator handles them) but will allow null values (because TreeMap does).

Iteration

  • Hashtable: The iterators returned by Hashtable (e.g., via keySet().iterator(), values().iterator()) are fail-fast. If the Hashtable is structurally modified (elements added/removed) by any thread after the iterator is created, except through the iterator’s own remove() method, the iterator will throw a ConcurrentModificationException.
  • Collections.synchronizedMap(): The iterators obtained from the wrapped map are not inherently thread-safe by themselves. If you need to iterate over the map (its keys, values, or entries) in a thread-safe manner, you must manually synchronize on the wrapper map object during the entire iteration block. Failure to do so can lead to ConcurrentModificationException or unpredictable behavior if another thread modifies the map concurrently.
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
// ... populate map ...

// MUST synchronize manually for safe iteration
synchronized (map) {
    for (Map.Entry<String, String> entry : map.entrySet()) {
        // Process entry
        System.out.println(entry.getKey() + "=" + entry.getValue());
    }
}

Performance

  • Both Hashtable and Collections.synchronizedMap() suffer from performance bottlenecks under high contention because they use a single lock for the entire map. Every access, whether read or write, requires acquiring this lock, limiting true concurrency.
  • Modern applications generally prefer java.util.concurrent.ConcurrentHashMap for thread-safe map operations, as it uses more sophisticated locking techniques (like lock striping) allowing multiple reads and potentially some writes to occur concurrently, leading to much better scalability.

Flexibility

  • Hashtable: Is a specific implementation.
  • Collections.synchronizedMap(): Can wrap any Map implementation (HashMap, TreeMap, LinkedHashMap, etc.), providing thread safety to different underlying data structures and ordering guarantees if applicable (like with LinkedHashMap or TreeMap).