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 theMapinterface, 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 existingMapimplementation by wrapping it.
Synchronization Mechanism
Hashtable: Achieves thread safety by synchronizing every public method (likeput(),get(),remove(),size(),containsKey(), etc.) on theHashtableobject itself. This is known as coarse-grained locking. Only one thread can execute any method on theHashtableinstance 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 toHashtable. Only one thread can execute a method via the wrapper at a time.
Null Keys and Values
Hashtable: Does not allownullkeys ornullvalues. Attempting to insert either will result in aNullPointerException.Collections.synchronizedMap(): The allowance ofnullkeys/values depends entirely on the underlying map that was wrapped.- If you wrap a
HashMap(Collections.synchronizedMap(new HashMap<>())), it will allow onenullkey and multiplenullvalues (becauseHashMapdoes). - If you wrap a
TreeMap(Collections.synchronizedMap(new TreeMap<>())), it generally won’t allownullkeys (unless a custom comparator handles them) but will allownullvalues (becauseTreeMapdoes).
- If you wrap a
Iteration
Hashtable: The iterators returned byHashtable(e.g., viakeySet().iterator(),values().iterator()) are fail-fast. If theHashtableis structurally modified (elements added/removed) by any thread after the iterator is created, except through the iterator’s ownremove()method, the iterator will throw aConcurrentModificationException.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 toConcurrentModificationExceptionor 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
HashtableandCollections.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.ConcurrentHashMapfor 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 anyMapimplementation (HashMap,TreeMap,LinkedHashMap, etc.), providing thread safety to different underlying data structures and ordering guarantees if applicable (like withLinkedHashMaporTreeMap).
