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 theMap
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 existingMap
implementation by wrapping it.
Synchronization Mechanism
Hashtable
: Achieves thread safety by synchronizing every public method (likeput()
,get()
,remove()
,size()
,containsKey()
, etc.) on theHashtable
object itself. This is known as coarse-grained locking. Only one thread can execute any method on theHashtable
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 toHashtable
. Only one thread can execute a method via the wrapper at a time.
Null Keys and Values
Hashtable
: Does not allownull
keys ornull
values. Attempting to insert either will result in aNullPointerException
.Collections.synchronizedMap()
: The allowance ofnull
keys/values depends entirely on the underlying map that was wrapped.- If you wrap a
HashMap
(Collections.synchronizedMap(new HashMap<>())
), it will allow onenull
key and multiplenull
values (becauseHashMap
does). - If you wrap a
TreeMap
(Collections.synchronizedMap(new TreeMap<>())
), it generally won’t allownull
keys (unless a custom comparator handles them) but will allownull
values (becauseTreeMap
does).
- If you wrap a
Iteration
Hashtable
: The iterators returned byHashtable
(e.g., viakeySet().iterator()
,values().iterator()
) are fail-fast. If theHashtable
is 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 toConcurrentModificationException
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
andCollections.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 anyMap
implementation (HashMap
,TreeMap
,LinkedHashMap
, etc.), providing thread safety to different underlying data structures and ordering guarantees if applicable (like withLinkedHashMap
orTreeMap
).