HashMap und Threads

paul10

Mitglied
Hallo,
Ich habe eine Frage bezüglich der Benutzung von Threads und HashMap.

Mir ist klar, dass sich dafür die ConcurrentHashMap verwenden soll, damit es Threadsafe ist.

Was mir allerdings nicht klar ist: Ich habe eine Überklasse, die zwei Threads verwaltet. Diese beiden Threads
benötigen die HasMap und geben sie an andere Klassen weiter.

Mein Lehrer hat mir aber mitgeteilt, dass genau das nicht möglich ist, da hierbei Fehler auftreten können und meinte
ich soll per get Methoden auf die HashMap zugreifen.

Mir ist jetzt nicht klar was damit gemeint ist(welchen Fehler kann ich damit beheben, es ist ja snychronisiert). Weiß vielleicht einer einen Tipp, bzw. eine andere Lösung um ein solches Problem
zu lösen?

Danke im Voraus!! :)
MfG
 
Mir ist klar, dass sich dafür die ConcurrentHashMap verwenden soll, damit es Threadsafe ist."

Wenn es so wäre: super! Leider ist das für einen Java Programmierer eine eher unzulässige Annahme. Damit eine Klasse als "Threadsafe" gilt, müssen alle Methoden, welche lesend und schreibend auf die Klasse zugreifen, synchronisiert sein, auch ein size()! Abgesehen davon, geht es nicht nur um die Synchronisierung, so auch darum, unnötige Deadlocks zu vermeiden. Da dies für eine Klasse schnell zur Komplexität eine Dissertation ausarten kann, muss man mit der Aussage "damit es Threadsafe ist" vorsichtig sein. Weitere Ausführungen findest Du hier: Java theory and practice: Building a better HashMap
 
Zuletzt bearbeitet:
Hi paul10,

[...]
Was mir allerdings nicht klar ist: Ich habe eine Überklasse, die zwei Threads verwaltet. Diese beiden Threads benötigen die HasMap und geben sie an andere Klassen weiter.

Mein Lehrer hat mir aber mitgeteilt, dass genau das nicht möglich ist, da hierbei Fehler auftreten können und meinte ich soll per get Methoden auf die HashMap zugreifen.
[...]

Falsch. Eine get() Methode liefert die Map zurück. Selbst wenn diese synchronized ist, ist dadurch nicht sichergestellt dass nur ein Thread da rein schreiben. Nur das Abholen der Map vom Verwalter ist synchronized, nicht der Zugriff.
So gesehen ist das equivalent wie die Übergabe der Map im Konstruktor der Threads.

Ob du das per Konstruktor oder get()-Methode machst hängt davon ab, was du design-technisch bevorzugst. Braucht die Unter-Thread Klasse einen Verweis auf den Verwalter? Denn dieser wird benötigt wenn du die get() aufrufen willst. Wenn dieser sowiso vorhanden ist, dann würde ich zu der get()-Lösung tendieren, ansonsten nicht.

Auch wenn du in zwei verschiedenen Thread-Instanzen folgendes machst, funktioniert es:
Java:
synchronized (meineHashMap) {
}

Das synchronized wird auf das gleiche Objekt durchgeführt, somit blockiert dieser Code-Teil, obwohl du in jedem Thread ein eigenes Attribut mit der HashMap hast. Diese verweisen allerdings alle auf ein und dasselbe Objekt.

[...]
Mir ist klar, dass sich dafür die ConcurrentHashMap verwenden soll, damit es Threadsafe ist.
[...]

Siehe Antwort von j2se, hier noch der Verweis auf die Dokumentation der Klasse:

http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentHashMap.html


// Edit: Hier noch ein Beispiel mit 10 Threads. Es wird hier nie vorkommen, dass 2 verschiedene Threads direkt hinterander "Im synchronized" ausgeben:

Java:
package de.tutorials.bk;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Application {
	static class MyThread extends Thread {
		private Map<String, String> hashmap;
		
		public MyThread(Map<String, String> instanz) {
			hashmap = instanz;
		}
		
		@Override
		public void run() {
			debug(getName() + ": Vor synchronized");
			synchronized (hashmap) {
				debug(getName() + ": Im synchronized");
			}
			debug(getName() + ": Nach synchronized");
		}
	}
	
	public static void debug(String msg) {
		synchronized (System.out) {
			System.out.println(msg);
		}
	}
	
	public static void main(String[] args) {
		Map<String, String> test = new HashMap<>();
		List<Thread> threads = new ArrayList<>();
		
		for(int i = 0; i < 10; i++) {
			threads.add(new MyThread(test));
		}

		for(int i = 0; i < 10; i++) {
			threads.get(i).start();
		}
	}
}

Gruß,
BK
 
Zuletzt bearbeitet:
Auch wenn du in zwei verschiedenen Thread-Instanzen folgendes machst, funktioniert es:
Java:
synchronized (meineHashMap) {
}

Das synchronized wird auf das gleiche Objekt durchgeführt, somit blockiert dieser Code-Teil, obwohl du in jedem Thread ein eigenes Attribut mit der HashMap hast. Diese verweisen allerdings alle auf ein und dasselbe Objekt.

Um Missverständnissen vorzubeugen: synchronized bezieht sich auf Methoden und nicht auf das verwendete Objekt! Stelle man sich zudem vor, dass eine Bank mehrere Konten in eine Collection verwaltet. Es wäre sehr ineffizient, wenn beim Zugriff auf ein Konto alle Konten blockiert werden und so alle Aufrufer auf die Freigabe der Sperre warten müssen. Stattdessen bietet es sich an, als Sperre nur gerade das benötigte Konto zu verwendet. Hier ein Beispiel aus dem Buch Parallele und verteilte Anwendungen in Java

Es folgt eine Beispiel aus obigem Buch, wo die Parallelität nicht unnötig eingeschränkt wird. Zuerst einmal die triviale Klasse Amount, welche mit einem Betrag von 1000 initialisiert wird:

Code:
public class Account {
	
	private float balance;
	
	public Account() {
		setBalance(1000);
	}

	public float getBalance() {
		return balance;
	}

	public void setBalance(float balance) {
		this.balance = balance;
	}
}

Die folgende Klasse Bank simuliert 10 Konten und beinhaltet auch die Methode tranferMoney, die gleichzeitig von mehreren Clerks aufgerufen werden kann. Als Besonderheit wird hier ein synchronized Block verwendet der als Sperre nur gerade das bearbeitete Konto verwendet. Zu Demo-Zwecken wird zwischen der Berechnung des neuen Saldos und dem Schreiben des neuen Saldos ein Sleep eingelegt. Es handelt sich nur um 100 Millis, für ein Quad-Prozessor ist das aber eine kleine Ewigkeit. Da sich nun alle Operationen innerhalb des synchronized Blocks befindet, ist das Konto für andere Clerks erst wieder entsperrt, wenn er vollständig abgearbeitet worden ist. So können keine Dateninkonsistenzen entstehen resp. das Konto > 0 werden.

Code:
public class Bank {
	private Account[] account;

	public Bank() {
		account = new Account[10];
		for (int i = 0; i < account.length; i++) {
			account[i] = new Account();
		}
	}

	public void tranferMoney(int accountNumber, float amount) {

		synchronized (account[accountNumber]) {
			float oldBalance = account[accountNumber].getBalance();
			float newBalance = oldBalance + amount;
			try {
				Thread.sleep(100);
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
			if (newBalance >= 0) {
				account[accountNumber].setBalance(newBalance);
			}
		}
	}

	public void printAccount() {
		for (int i = 0; i < account.length; i++) {
			System.out.println("Account " + i + " balance: "
					+ account[i].getBalance());
		}
	}
}

Und last but not least die Klasse Clerk resp. Bankangestellter.

Code:
public class Clerk extends Thread {
	private Bank bank;

	public Clerk(String name, Bank bank) {
		super(name);
		this.bank = bank;
		this.start();
	}

	public void run() {
		for (int i = 0; i < 10; i++) {
			// Zufällige Kontonummer generieren
			int accountNumber = (int) (Math.random() * 10);
			// Betrag generieren zwischen -500 und 499
			float amount = (int) (Math.random() * 1000) - 500;
			bank.tranferMoney(accountNumber, amount);
		}
	}

	public static void main(String[] args) {
		Clerk[] clerk = new Clerk[50];
		Bank bank = new Bank();

		for (int i = 0; i < clerk.length; i++) {
			clerk[i] = new Clerk("Clerk " + i, bank);
			try {
				clerk[i].join();
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		}

		bank.printAccount();
	}
}

Darin wird eine Bank erzeugt und 50 Clerks, welche sich die gleiche Bank resp. die gleichen Konti teilen. Der Thread Clerk wird innerhalb des Konstruktors gestartet und führt 10 Mal auf zufällig ausgewählte Konti mit zufälligen Beträgen Transaktionen aus.

Ich hoffe, das Prinzip ist klar geworden. Threads gehören in Java zu den komplexeren Themen. Also, nicht gleich verzweifeln, sondern ein gutes Buch zur Rate ziehen.
 
Zurück