Dies ist ein Problem, auf das ich häufig stoße, wenn ich mit komplexeren Systemen arbeite und das habe ich nie gut gelöst. Es beinhaltet normalerweise Variationen über das Thema eines gemeinsamen Objekts, dessen Konstruktion und Initialisierung notwendigerweise zwei verschiedene Schritte sind. Dies ist im Allgemeinen aufgrund von Architekturanforderungen ähnlich wie Applets, so dass Antworten, die vorschlagen, dass ich Konstruktion und Initialisierung konsolidiere, nicht nützlich sind. Die Systeme müssen spätestens auf Java 4 ausgerichtet sein, so dass Antworten, die eine Unterstützung vorschlagen, die nur in späteren JVMs verfügbar ist, ebenfalls nicht sinnvoll sind.
Nehmen wir als Beispiel eine Klasse, die so strukturiert ist, dass sie in ein Anwendungsframework passt:
%Vor%Ich kann das someObject-Finale nicht machen, da es nicht gesetzt werden kann, bis startup () aufgerufen wird. Aber ich möchte wirklich, dass es seine Einmalschreib-Semantik widerspiegelt und in der Lage ist, direkt von mehreren Threads darauf zuzugreifen, vorzugsweise um eine Synchronisation zu vermeiden.
Um einen Endgültigkeitsgrad auszudrücken und durchzusetzen, vermute ich, dass ich einen generischen Container erstellen könnte ( UPDATE - korrigierte Threading-Klassen dieser Klasse ):
%Vor% und dann in MyClass
, oben:
Was einige Fragen für mich aufwirft:
Zweitens in Bezug auf die Fadensicherheit:
someObject.get()
zugreift, nachdem sein set()
aufgerufen wurde? Die anderen Threads rufen nur Methoden auf MyClass zwischen startup () und shutdown () auf - das Framework garantiert dies. WormReference
ist es unter beiden JMM immer möglich, einen Wert von object
zu sehen, der weder Null noch ein Verweis auf ein SomeObject ist. Mit anderen Worten, garantiert das JMM immer, dass kein Thread den Speicher eines Objekts so beobachten kann, dass es sich auf die Werte bezieht, die sich bei der Zuweisung des Objekts auf dem Heapspeicher befanden. Ich glaube, die Antwort ist "Ja", da die Zuweisung den zugewiesenen Speicher explizit auf Null setzt - aber kann CPU-Caching dazu führen, dass an einem bestimmten Speicherort etwas anderes beobachtet wird? Beachten Sie, dass die primäre Stoßrichtung dieser Frage darin besteht, die Endgültigkeit von someObject
auszudrücken und durchzusetzen, ohne sie tatsächlich markieren zu können. final
; Sekundär ist, was für die Fadensicherheit notwendig ist. Das heißt, nicht auf den Thread-Sicherheitsaspekt davon hängen.
Dies ist meine letzte Antwort, Regis 1 :
%Vor%(1) Überbringen Sie die Spielshow "So werden Sie Millionär" von Regis Philburn.
Ich würde damit beginnen, Ihre someObject
volatile
zu deklarieren.
Das flüchtige Schlüsselwort erzeugt eine Speicherbarriere, was bedeutet, dass separate Threads immer den aktualisierten Speicher sehen, wenn sie someObject
referenzieren.
In Ihrer aktuellen Implementierung sehen einige Threads möglicherweise noch ein Objekt als null
, selbst nachdem startup
aufgerufen wurde.
Tatsächlich wird diese volatile
-Technik häufig von Sammlungen verwendet, die in java.util.concurrent
package deklariert sind.
Und wie einige andere Poster hier andeuten, wenn alles andere fehlschlägt, wird auf vollständige Synchronisation zurückgegriffen.
Erwägen Sie die Verwendung von AtomicReference als Delegieren Sie in diesem Objekt-Container, den Sie erstellen möchten. Zum Beispiel:
%Vor% EDITED: Das wird einmal mit einer Lazy-Initialisierungsmethode gesetzt. Es ist nicht perfekt, um mehrere Anrufe zu einem (vermutlich teuren) init()
zu blockieren, aber es könnte schlimmer sein. Sie könnten die Instanziierung von myBar
in den Konstruktor stecken und später einen Konstruktor hinzufügen, der auch die Zuweisung zulässt, sofern die richtigen Informationen angegeben werden.
Es gibt einige allgemeine Diskussionen über thread-safe, Singleton Instanziierung (die Ihrem Problem sehr ähnlich ist) zum Beispiel bei diese Seite .
Theoretisch würde es ausreichen, startup()
wie folgt neu zu schreiben:
Übrigens können Threads auch WoRmObject
mehrfach aufrufen, obwohl set()
endgültig ist. Sie müssen wirklich eine Synchronisierung hinzufügen.
update : Ich habe ein bisschen herumgespielt und ein SSCCE erstellt, du findest es vielleicht nützlich, ein bisschen zu spielen rum damit:)
%Vor% Die CountDownLatch
verursacht alle await()
ruft auf, um zu blockieren, bis der Countdown Null erreicht.
Es ist höchstwahrscheinlich threadsicher, aus Ihrer Beschreibung des Frameworks. Zwischen dem Aufruf von myobj.startup()
und der Bereitstellung von myobj
für andere Threads muss eine Speicherbarriere bestehen. Dies garantiert, dass die Schreibvorgänge in startup () für andere Threads sichtbar sind. Daher müssen Sie sich nicht um die Thread-Sicherheit kümmern, da das Framework dies tut. Es gibt jedoch kein kostenloses Mittagessen; Immer wenn ein anderer Thread über das Framework Zugriff auf myobj
erhält, muss er synchron oder flüchtig gelesen werden.
Wenn Sie in das Framework schauen und den Code im Pfad auflisten, sehen Sie sync / volatile an den richtigen Stellen, die Ihren Code threadsicher machen. Das heißt, wenn das Framework korrekt implementiert ist.
Betrachten wir ein typisches Swing-Beispiel, in dem ein Worker-Thread eine Berechnung durchführt, die Ergebnisse in einer globalen Variablen x
speichert und dann ein Repaint-Ereignis sendet. Der GUI-Thread liest nach Erhalt des Repaint-Ereignisses die Ergebnisse aus der globalen Variablen x
und aktualisiert entsprechend neu.
Weder der Worker-Thread noch der Repaint-Code führen eine Synchronisierung oder flüchtiges Lesen / Schreiben von irgendetwas durch. Es muss Zehntausende solcher Implementierungen geben. Zum Glück sind sie alle Thread-sicher, obwohl die Programmierer keine besondere Aufmerksamkeit geschenkt haben. Warum? Weil die Ereigniswarteschlange synchronisiert ist; Wir haben eine nette Happenings-Before-Kette:
%Vor% Deshalb schreiben Sie x
und lesen x
richtig synchronisiert, implizit über Event-Framework.
Könnten Sie ein ThreadLocal verwenden, das nur den Wert jedes Threads einmal zulässt?
Es gibt eine Menge falscher Methoden, um faule Instanziierung durchzuführen, besonders in Java.
Kurz gesagt besteht der naive Ansatz darin, ein privates Objekt, eine öffentliche synchronisierte Init-Methode und eine öffentliche unsynchronisierte get-Methode zu erstellen, die eine Null-Überprüfung für Ihr Objekt durchführt und bei Bedarf init aufruft. Die Feinheiten des Problems kommen bei der Durchführung der Null-Überprüfung auf eine Thread-sichere Weise.
Dieser Artikel sollte nützlich sein: Ссылка
Dieses spezielle Thema in Java wird ausführlich in Doug Leas "Concurrent Programming in Java" behandelt, das etwas veraltet ist, und in "Java Concurrency in Practice", das von Lea und anderen Autoren verfasst wurde. Insbesondere wurde CPJ vor der Veröffentlichung von Java 5 veröffentlicht, wodurch die Parallelität von Java erheblich verbessert wurde.
Ich kann genauere Informationen veröffentlichen, wenn ich nach Hause komme und Zugang zu diesen Büchern habe.
Erste Frage: Warum können Sie nicht einfach eine private Methode starten, die im Konstruktor aufgerufen wird, dann kann sie endgültig sein. Dies würde die Threadsicherheit nach dem Aufruf des Konstruktors sicherstellen, da er vorher unsichtbar ist und erst gelesen wird, nachdem der Konstruktor zurückgegeben wurde. Oder faktorisieren Sie die Klassenstruktur neu, damit die Startmethode das MyClass-Objekt als Teil des Konstruktors erstellen kann. In einigen Fällen scheint dieser spezielle Fall ein Fall von schlechter Struktur zu sein, wo Sie wirklich nur endgültig und unveränderlich machen wollen.
Der einfache Ansatz, wenn die Klasse unveränderlich ist und erst gelesen wird, nachdem sie erstellt wurde, dann wickle sie in eine unveränderbare Liste von Guava. Sie können auch einen eigenen unveränderlichen Wrapper erstellen, der defensiv kopiert, wenn Sie aufgefordert werden, die Referenz zurückzugeben, sodass ein Client die Referenz nicht ändern kann. Wenn es intern unveränderlich ist, ist keine weitere Synchronisation erforderlich, und unsynchronisierte Lesevorgänge sind zulässig. Sie können Ihren Wrapper so einstellen, dass er auf Anfrage defensiv kopiert wird, so dass selbst Versuche, auf ihn zu schreiben, sauber fehlschlagen (er tut einfach nichts). Möglicherweise benötigen Sie eine Speicherbarriere, oder Sie können eine lazy-Initialisierung durchführen. Beachten Sie jedoch, dass die faule Initialisierung möglicherweise eine weitere Synchronisierung erfordert, da Sie während der Konstruktion des Objekts möglicherweise mehrere unsynchronisierte Leseanforderungen erhalten.
Der etwas kompliziertere Ansatz würde die Verwendung einer Aufzählung beinhalten. Da Aufzählungen Singleton garantiert sind, ist sie, sobald die Aufzählung erstellt wird, für immer fixiert. Sie müssen weiterhin sicherstellen, dass das Objekt intern unveränderlich ist, aber es garantiert seinen Singleton-Status. Ohne viel Aufwand.
Die folgende Klasse könnte Ihre Frage beantworten. Eine gewisse Thread-Sicherheit wurde erreicht, indem eine volatile
Zwischenvariable in Verbindung mit final
value keeper im bereitgestellten generic verwendet wurde. Sie können eine weitere Erhöhung durch Verwendung von synchronized
setter / getter in Erwägung ziehen. Hoffe es hilft.
Tags und Links java multithreading