W3docs

Java Hashtable

Die legacy-synchronisierte Hashtable in Java, warum sie durch HashMap und ConcurrentHashMap abgelöst wurde und wann sie noch auftaucht.

Hashtable<K, V> ist die ursprüngliche hash-basierte Map in Java, die bis auf JDK 1.0 aus dem Jahr 1996 zurückgeht — zwei Jahre vor der Einführung des Collections Frameworks. Als Map, HashMap und die übrigen Klassen in JDK 1.2 hinzukamen, wurde Hashtable nachträglich so angepasst, dass es Map implementiert, aber seine Eigenheiten blieben erhalten: jede Methode ist synchronized, sowohl Schlüssel als auch Werte lehnen null ab, und die öffentliche API enthält Pre-Collections-Methoden (elements(), keys()), die vor Iterator existierten.

In neuem Code möchte man sie fast nie verwenden. Dieses Kapitel ist dazu da, damit Sie die Klasse erkennen, wenn Sie ihr begegnen, verstehen, warum sie noch existiert, und wissen, was Sie stattdessen verwenden sollten.

Warum sie noch existiert

Drei Gründe:

  1. Abwärtskompatibilität. Eine Handvoll Klassen der Standardbibliothek gibt Hashtable zurück — System.getProperties() gibt eine Properties-Instanz zurück, die Hashtable<Object, Object> erweitert. Einige alte JNDI-APIs (InitialContext(Hashtable)) nehmen sie als Argument.
  2. Vorhandener Code. Jede Codebasis, die älter als etwa 2005 ist, kann noch Hashtable an Stellen enthalten, an denen niemand eine Migration vornehmen wollte.
  3. Fehlgeleitete Vertrautheit. Sie taucht in Interviews und Tutorials auf, und Anfänger greifen manchmal danach, weil sie "eine thread-sichere Map möchten" — ohne von ConcurrentHashMap zu wissen.

Unterschiede zu HashMap

Hashtable und HashMap sind beide Hash-Tabellen mit Chaining, implementieren beide Map<K, V> und haben beide O(1) erwartet für get/put/remove. Die Unterschiede:

MerkmalHashtableHashMap
Thread-Sicherheitjede Methode synchronized auf die gesamte Tabellenicht thread-sicher
null-Schlüsselabgelehnt (NullPointerException)einer erlaubt
null-Wertabgelehnt (NullPointerException)viele erlaubt
Iterationsreihenfolgenicht festgelegtnicht festgelegt
Standardkapazität1116
Kapazitätswachstum2*old + 1 (ungerade Größen, langsameres Modulo)verdoppelt auf die nächste Zweierpotenz
Pre-Collections-APIelements(), keys() Enumerationskeine
Java 8 Baumifizierungneinja — Buckets werden bei über 8 Einträgen zu Bäumen
Fail-Fast-Iteratorenja, seit Retrofit 1.2ja
clone()ja (flach)ja (flach)

Die Synchronisierung ist der entscheidende Unterschied und der Hauptgrund, warum Hashtable langsam ist: jeder Lese- und jeder Schreibzugriff erwirbt die gleiche Sperre auf die gesamte Tabelle. Ein Multithreading-Programm mit zwei Threads, die nichts anderes tun als get auf einer Hashtable, wird serialisiert — sie wechseln sich mit der Sperre ab.

Warum synchronized auf jeder Methode keine echte Thread-Sicherheit ist

Ein überraschend häufiger Fehler: Entwickler sehen "jede Methode ist synchronized" und denken, Hashtable macht ihren Multithreading-Code korrekt. Das tut es nicht. Zusammengesetzte Operationen sind immer noch fehlerhaft:

if (!table.containsKey(key)) {       // synchronized
  table.put(key, computeValue());    // synchronized — but separate lock acquisition
}

Zwischen den beiden Aufrufen kann ein anderer Thread den gleichen Schlüssel mit put einfügen. Beide Threads sehen, dass containsKey false zurückgibt, beide berechnen, beide führen put aus. Sie erhalten zwei Auswertungen und der falsche Wert gewinnt.

Die heutige Lösung besteht nicht darin, Hashtable zu reparieren, sondern ConcurrentHashMap zu verwenden, das atomare zusammengesetzte Operationen eingebaut hat: putIfAbsent, computeIfAbsent, merge, replace(k, old, new). Sie erwerben intern die richtigen Sperren und führen das Test-und-Setzen als eine einzige Operation durch.

Was man stattdessen verwenden sollte

Der Entscheidungsablauf, wenn Sie versucht sind, new Hashtable<>() zu schreiben:

  • Single-Thread-Code, Sie möchten eine MapHashMap. Fertig. Der synchronized-Overhead von Hashtable ist reiner Kostenpunkt ohne Nutzen.
  • Multithreading-Code, Sie möchten eine MapConcurrentHashMap. Lock-striped (lock-frei für Lesezugriffe in modernen JDKs), keine globale Sperre, atomare zusammengesetzte Operationen, weit bessere Skalierbarkeit.
  • Multithreading-Code, Sie benötigen wirklich jede Operation atomar mit allem anderenCollections.synchronizedMap(new HashMap<>()). Gleiche Einzelsperren-Verhalten wie Hashtable, verbindet sich aber mit der modernen Collections-API. Immer noch schlechter als ConcurrentHashMap, wenn Sie das verwenden können.
  • Sie sehen eine API, die Hashtable erfordert (Properties, JNDI) → verwenden Sie die Hashtable, weil die API es erfordert; führen Sie keine parallele ein.

Die Pre-Collections-Eigenheiten

Hashtable existierte vor Iterator und stellt stattdessen Enumeration<K> bereit:

Enumeration<String> keys = table.keys();
while (keys.hasMoreElements()) {
  System.out.println(keys.nextElement());
}

Enumeration hat nur hasMoreElements() und nextElement() — kein remove(). Der Retrofit in 1.2 fügte keySet(), entrySet() und values() aus Map hinzu, und Sie können diese mit einem normalen Iterator durchlaufen. Da aber beide APIs auf demselben Objekt vorhanden sind, sehen Sie beide Stile in der Praxis. Bevorzugen Sie die Map-Ansicht; das ist die Sprache, die Sie bereits kennen.

Ein ausgearbeitetes Beispiel: Hashtable, warum sie Nulls ablehnt und die Race Condition, gegen die sie nicht schützt

Das folgende Programm demonstriert die sichtbaren Unterschiede zu HashMap — die synchronisierten Methoden, die abgelehnten Nulls, die Legacy-Enumeration — und zeigt die Check-then-Act-Race Condition, die die Synchronisierung von Hashtable nicht löst.

java— editable, runs on the server

Was man aus der Ausführung mitnehmen kann:

  • Die grundlegende API ist die von Map, daher sieht Hashtable bei einfacher Verwendung wie HashMap aus. Identische Ergebnisse, aber langsamer.
  • Nulls werden auf beiden Seiten abgelehnt — das ist die einzige Map in der JDK-Familie, die null-Werte sowie null-Schlüssel ablehnt.
  • Der Hashtable-Zähler ist falsch. Jede Methode ist synchronized, aber get dann put sind zwei separate atomare Operationen, nicht eine. Threads konkurrieren dazwischen und verlieren Aktualisierungen.
  • Die ConcurrentHashMap-Version mit merge ist korrekt und schnell. Das ist das richtige Werkzeug für "thread-sichere Map" im Jahr 2026.

Was kommt als nächstes

Hashtable hat einen Nachkommen, den Sie tatsächlich verwenden werden: Properties, den Konfigurationscontainer hinter System.getProperties() und dem .properties-Dateiformat. Es ist in seinem Anwendungsbereich eng und angenehm zu verwenden; es ist das nächste Kapitel und das letzte "Datenstruktur"-Kapitel in diesem Teil des Buches, bevor wir zu Iteration, Sortierung und den statischen Hilfsmethoden übergehen.

Übungen

Übung
Ihr leitender Ingenieur bittet Sie, eine `Hashtable<String, Integer>` aus einem Multithreading-Zähler zu entfernen, der `int n = t.get(k); t.put(k, n + 1);` ausführt. Was ist der richtige Ersatz?
Ihr leitender Ingenieur bittet Sie, eine `Hashtable<String, Integer>` aus einem Multithreading-Zähler zu entfernen, der `int n = t.get(k); t.put(k, n + 1);` ausführt. Was ist der richtige Ersatz?
Was this page helpful?