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.