[Java] Ist eine Java HashMap Thread Safe?

Ist eine Java HashMap Thread Safe?

HashMap Code

In diesem Artikel wollen wir uns einmal damit beschäftigen, ob die Map Implementierung HashMap<Key,Value> Thread Safe ist. Sicherlich habt ihr auch schon einmal von einer ConcurrentHashMap gehört und euch gefragt, was denn jetzt der Unterschied zwischen diesen beiden ist. Auch gibt es bei einigen immer wieder etwas Verwirrung, weil man immer mal was von 16 Locks im Zusammenhang mit einer HashMap gehört hat. Heute wollen wir das Rätsel einmal auflösen.

Ist eine HashMap Thread Safe?

Eine HashMap ist Thread Safe, solange sich die Struktur nicht ändert.

“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.”

– Quelle: http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html

 

Was bedeutet das konkret?

  1. Ihr könnt lesend (z.B. mit der get Methode) aus mehreren Threads auf eure HashMap zugreifen
  2. Solange ihr keine neuen Keys anlegt oder löscht (also quasi nur bestehende Werte überschreibt), bleibt die Struktur gleich –> Thread Safe
  3. Sobald ihr aber einen neuen Key hinzufügt oder löscht, ist das ganze nicht mehr Thread Safe

 

Reicht ein volatile, um eine HashMap Thread Safe zu bekommen?

Ist diese HashMap jetzt auch bei einer ändernden Struktur Thread Safe?
Leider nicht. Das volatile bezieht sich nur auf die Instanz der HashMap, nicht aber auf deren Struktur, sodass dies in unserem Fall keinen Unterschied macht.

 

Wie bekommt man die HashMap Thread Safe?

Dafür gibt es mehrere Möglichkeiten.

Zuallererst gibt es eine Methode synchronizedHashMap(), um eine HashMap zu synchronisieren.

Dies führt zwar dazu, dass diese Map jetzt Thread Safe ist, aber die Threads können nicht wirklich gleichzeitig auf diese zugreifen, sondern müssen dies nacheinander tun.

synchronized führt dazu, dass alle anderen Threads warten müssen, wenn ein anderer Thread auf dieses Objekt zugreift – solange, bis dieser eine Thread damit fertig ist.

Wenn gerade ein Thread auf der Map iteriert und die Map Struktur verändert wird, wird sogar eine ConcurrentModificationException geworfen.

 

Gibt es eine bessere Möglichkeit ohne synchronized?

Zusätzlich zur HashMap Implementierung gibt es eine sog. ConcurrentHashMap.

Eine ConcurrentHashMap ist komplett Thread Safe, ohne einen synchronized Block verwenden zu müssen.

Reads benötigen keine Locks, weshalb sie sogar viele READ Zugriffe gleichzeitig ausführen kann, lediglich WRITE Zugriffe benötigen einen exklusiven Write Lock, wobei bei einem Write Zugriff die Read Zugriffe warten müssen. Die Implementierung nutzt hierfür mehrere Locks, aber nicht einen Lock für jedes Objekt, sondern quasi jeweils einen Lock für eine Objekt Gruppe (eine HashMap nutzt mehrere LinkedLists und ist wie ein Tree aufgebaut). Eine ConcurrentHashMap wirft auch keine ConcurrentModificationException.

Intern unterteilt die ConcurrentHashMap das Array wie ein Tree und vergibt für jeden Tree einen WriteLock.

Außerdem ist die Klasse so designt, dass lediglich ein Thread gleichzeitig auf einer Map iterieren kann.

Standardmäßig nutzt die Klasse ConcurrentHashMap 16 Locks, womit 16 Threads gleichzeitig auf diese Map zugreifen können.

 

Dennoch gibt es eine Besonderheit:

Eine HashMap unterstützt null Keys und null Values – eine ConcurrentHashMap nicht!

Wenn die get() Methode ein null zurückgibt, bedeutet dies, dass der Key nicht in der Map vorhanden ist.

 

Performance der ConcurrentHashMap

Da jeder Lock dazu führen kann, das irgendein Thread warten muss und die ConcurrentHashMap lediglich Looks für Writes benötigt, sind viele Lese Zugriffe um einiges schneller als bei einer HashMap und können im Gegensatz zu dieser auch parallel auf das selbe Object durchgeführt werden, da die ConcurrentHashMap volatile nutzt. Schreibzugriffe sind zwar nicht so schnell wie Read Zugriffe, aber ebenfalls Thread safe. Wenn gerade ein Write Lock vergeben wurde, müssen aber die Read Zugriffe ebenfalls warten. Aus diesem Grund kann die ConcurrentHashMap diese Zugriffe nochmal priorisieren, um z.B. erst alle Reads auszuführen und dann das Object erst zu verändern.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.