synchronized
bedeutet, dass, wenn mehrere Threads gleichzeitig versuchen, dieses Stück Code auszuführen, nur einer dieser Threads zu einem bestimmten Zeitpunkt innerhalb des Blocks erlaubt ist. synchronized (listeners)
verwendet listeners
als Sperrkennzeichen, was bedeutet, dass diese Einschränkung für alle Blöcke gilt, die sich auf diese Variable synchronisieren - wenn sich ein Thread in einem dieser Blöcke befindet, darf kein anderer Thread einen dieser Blöcke betreten.
Auch wenn es nur einen einzigen Funktionsaufruf in einem Block gibt, kann dies sinnvoll sein: Diese Funktion besteht aus vielen anderen Anweisungen, und die Kontrolle kann zu einem anderen Thread wechseln, während sich der erste inmitten dieser Funktion befindet. Wenn die Funktion nicht thread-safe Das kann zu Problemen führen, z. B. zum Überschreiben von Daten.
In diesem speziellen Fall besteht der Funktionsaufruf darin, einen Wert zu einer Sammlung hinzuzufügen listeners
. Es ist zwar nicht unmöglich, eine thread-sichere Sammlung zu erstellen, aber die meisten Sammlungen sind nicht thread-sicher für mehrere Schreiber. Um also sicherzustellen, dass die Sammlung nicht durcheinander gerät, ist es notwendig, synchronized
erforderlich ist.
EDIT: Um ein Beispiel dafür zu geben, wie die Dinge durcheinander geraten können, nehmen Sie diese vereinfachte Implementierung von add
, wobei length
ist die Anzahl der Elemente in der items
Array:
public void Add(T item) {
items[length++] = item;
}
Das length++
Bit ist nicht atomar; es besteht aus einem Lesen, einem Inkrement und einem Schreiben, und der Thread kann nach jedem dieser Schritte unterbrochen werden. Lassen Sie uns das also ein wenig umschreiben, um zu sehen, was wirklich passiert:
public void Add(T item) {
int temp = length;
length = length + 1;
items[temp] = item;
}
Nehmen wir nun an, dass zwei Threads T1 und T2 gleichzeitig in Add eintreten. Hier ist ein möglicher Satz von Ereignissen:
T1: int temp = length;
T2: int temp = length;
T2: length = length + 1;
T2: items[temp] = item;
T1: length = length + 1;
T1: items[temp] = item;
Das Problem dabei ist, dass den gleichen Wert wird verwendet für temp
von beiden Threads, so dass der letzte Thread, der den Thread verlässt, am Ende das Element überschreibt, das der erste dort abgelegt hat; と Es gibt einen nicht zugewiesenen Posten ganz am Ende.
Es hilft auch nicht, wenn length
stellt den nächsten zu verwendenden Index dar, so dass wir ein Preincrement verwenden können:
public void Add(T item) {
items[++length] = item;
}
Wir schreiben das noch einmal um:
public void Add(T item) {
length = length + 1;
items[length] = item;
}
Dies ist eine mögliche Abfolge von Ereignissen:
T1: length = length + 1;
T2: length = length + 1;
T2: items[length] = item;
T1: items[length] = item;
Auch hier überschreibt der letzte Thread den ersten, aber jetzt ist das nicht zugewiesene Element das vorletzte Element.