Wie erstelle ich in Java einen threadsicheren Einmallesewert?

9

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:

%Vor%

Was einige Fragen für mich aufwirft:

  1. Gibt es einen besseren Weg, oder existierendes Java-Objekt (müsste in Java 4 verfügbar sein)?

Zweitens in Bezug auf die Fadensicherheit:

  1. Ist dieses thread-sichere bereitgestellt , dass kein anderer Thread auf 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.
  2. Angesichts des vollständig unsynchronisierten Containers 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?
  3. Reicht es aus, WormRef.reference flüchtig zu machen, um eine korrekte Multithread-Semantik sicherzustellen?

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.

    
Lawrence Dol 29.08.2014, 18:09
quelle

12 Antworten

0

Dies ist meine letzte Antwort, Regis 1 :

%Vor%

(1) Überbringen Sie die Spielshow "So werden Sie Millionär" von Regis Philburn.

    
Lawrence Dol 10.04.2010, 03:50
quelle
1

Ich würde damit beginnen, Ihre someObject volatile zu deklarieren.

%Vor%

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.

    
Alexander Pogrebnyak 11.03.2010 21:30
quelle
1

Ich würde die Setter-Methode in WoRmObject entfernen und eine synchronisierte init () -Methode bereitstellen, die eine Ausnahme if (object != null)

auslöst     
crowne 11.03.2010 22:39
quelle
1

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 .

    
Carl 12.03.2010 02:06
quelle
1

Theoretisch würde es ausreichen, startup() wie folgt neu zu schreiben:

%Vor%

Ü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.

    
BalusC 11.03.2010 21:22
quelle
1

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.

    
irreputable 12.03.2010 03:39
quelle
0
  1. Wie wäre es mit der Synchronisation?
  2. Nein, es ist nicht threadsicher. Ohne Synchronisierung wird der neue Status Ihrer Variablen möglicherweise nicht an andere Threads weitergegeben.
  3. Ja, soweit ich weiß, sind Referenzen atomar, so dass Sie entweder Null oder die Referenz sehen. Beachten Sie, dass der Status des referenzierten Objekts eine komplett andere Geschichte ist als
Jens Schauder 11.03.2010 21:27
quelle
0

Könnten Sie ein ThreadLocal verwenden, das nur den Wert jedes Threads einmal zulässt?

    
Andrew Swan 11.03.2010 23:50
quelle
0

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.

    
Greg Howell 12.03.2010 03:35
quelle
0

Nur meine kleine Version basiert auf AtomicReference. Es ist wahrscheinlich nicht das beste, aber ich glaube, es ist sauber und einfach zu bedienen:

%Vor%     
jartur 14.05.2012 13:59
quelle
0

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.

    
phil_20686 19.05.2014 11:58
quelle
-1

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.

Ссылка

    
Tetyana Kolodyazhna 23.05.2017 10:28
quelle

Tags und Links