Was bedeuten die Modifikatoren synchronized
und volatile
?
Es kann leicht geschehen, dass sich, bei der gleichzeitigen Ausführung mehrerer Threads auf einer Methode des selben Objektes, deren Ausführungen überschneiden und so zu unkontrollierten Ergebnissen führen. Das folgende Beispiel zeigt, wie die Synchronisation von Methoden dies verhindern kann.
In der Klasse SyncCounter
wird eine einfache int
-Variable
deklariert und mit 0 initialisiert. Sie wird mit dem
Schlüsselwort volatile
gekennzeichnet damit
mehrere Threads gleichzeitig auf sie zugreifen können. Der Wert
der Variablen kann über die Methoden inc()
und dec()
inkrementiert und dekrementiert, sowie über einen Getter
zurückgegeben werden.
public class SyncCounter {
private volatile int i = 0;
public synchronized void inc() throws InterruptedException {
Thread.sleep(6);
i++;
}
public synchronized void dec() throws InterruptedException {
Thread.sleep(6);
i--;
}
public synchronized int getI() {
return i;
}
}
In den ersten beiden Methoden wird über das Pausieren des
zugreifenden Threads für 6 Millisekunden eine kurze
Unterbrechung provoziert, die es durch das etwas größere
Zeitfenster anderen Threads leichter ermöglicht, auf die
Variable zuzugreifen.
Alle Methoden sind als synchronized
gekennzeichnet. Dies bewirkt, dass die Ausführung der Methode
als atomar, also nicht unterbrechbar, behandelt wird. Wird also z.B.
die Methode inc()
aufgerufen, so pausiert der
aufrufende Thread für sechs Millisekunden und inkrementiert
dann i
bevor die Methode terminiert. Erst dann kann auf
dem selben Objekt eine weitere Methode aufgerufen werden.
public class SynchronizedClass {
public static void main(String[] args) throws InterruptedException {
countMultiple();
}
static void countMultiple() throws InterruptedException {
SyncCounter c = new SyncCounter();
for (int i = 0; i < 100; i++) {
new Thread() {
public void run() {
try {
c.inc();
c.dec();
} catch (InterruptedException e) {
}
}
}.start();
}
Thread.sleep(2000);
System.out.println("Final: " + c.getI());
}
}
Die Klasse SynchronizedClass
ruft in main()
die Methode countMultiple()
auf. In ihr wird
zunächst ein Objekt vom Typ SyncCounter
erzeugt.
In einer Schleife werden daraufhin eine Reihe an Threads erzeugt,
von denen jeder versucht, auf die Methoden inc()
und dec()
des Objektes zuzugreifen und hierüber den Variablenwert des
Objektes zu ändern. Wie oben erläutert, geschieht dies bei
den hier vorliegenden als synchronized
gekennzeichneten
Methoden streng nacheinander.
Nach Durchlaufen der Schleife ruht
der Haupt-Thread noch zwei Sekunden, um das sukzessive Abarbeiten
der Neben-Threads zu ermöglichen, bevor der Endwert der
Variablen ausgegeben wird. Da bei jedem Schleifendurchlauf der Wert
einmal inkrementiert und einmal dekrementiert wird, ist dieser nach
Abschluss gleich 0
.
Die genaue Bedeutung des Schlüsselwortes synchronized
wird deutlich, wenn man es aus den Methodendeklarationen entfernt,
das Programm erneut kompiliert und dann ausführt:
public void inc() throws InterruptedException { //... } public void dec() throws InterruptedException { //... } public int getI() { return i; }
Die ausgegebenen Ergebnisse unterscheiden sich nun von Mal zu Mal
und belaufen sich nur gelegentlich auf den erwarteten Wert 0
,
sondern nehmen wechselnde positive und negative Werte an.
Die
Erklärung: Alle drei Methodenausführungen werden nicht
mehr atomar durchgeführt. Vielmehr können sich die
Methodenaufrufe der einzelnen Threads überschneiden, sodass es
zu unregelmäßigen, nicht kalkulierbaren Inkrementierungen
und Dekrementierungen kommt.
Eine weiter in die Materie einführende, gleichwohl ausgezeichnete deutsche Darstellung des Synchronisierungsprinzips findet sich unter dem Titel Java Multithread Support - Basics in den Aufsätzen zum Concurrent Programming von Angelika Langer und Klaus Kreft.
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.